Began separating Generic Nextcloud API class from the Notes functionality.

This commit is contained in:
Scharel Clemens 2021-01-25 18:18:32 +01:00
parent d6682b7b69
commit bd116a8c15
7 changed files with 319 additions and 36 deletions

View file

@ -18,10 +18,12 @@ DEFINES += APP_VERSION=\\\"$$VERSION\\\"
HEADERS += src/note.h \
src/accounthash.h \
src/nextcloudapi.h \
src/notesapi.h \
src/notesmodel.h
SOURCES += src/harbour-nextcloudnotes.cpp \
src/nextcloudapi.cpp \
src/note.cpp \
src/notesapi.cpp \
src/notesmodel.cpp

View file

@ -190,7 +190,7 @@ Dialog {
automaticCheck: true
onCheckedChanged: {
if (!checked) {
verifyServerTimer.restart()
notesApi.getNcStatus()
}
}
}

6
src/nextcloudapi.cpp Normal file
View file

@ -0,0 +1,6 @@
#include "nextcloudapi.h"
NextcloudApi::NextcloudApi(QObject *parent) : QObject(parent)
{
}

262
src/nextcloudapi.h Normal file
View file

@ -0,0 +1,262 @@
#ifndef NEXTCLOUDAPI_H
#define NEXTCLOUDAPI_H
#include <QObject>
#include <QJsonObject>
#include <QVersionNumber>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFile>
#include <QTimer>
#include <QDebug>
// Nextcloud instance information
const QString STATUS_ENDPOINT("/status.php");
// Capabilites and users
const QString CAPABILITIES_ENDPOINT("/ocs/v1.php/cloud/capabilities");
const QString USERS_ENDPOINT("/ocs/v1.php/cloud/users");
const QString USER_CAPABILITIES_ENDPOINT("/ocs/v1.php/cloud/users/%1");
// Login and authentication
const QString GET_APPPASSWORD_ENDPOINT("/ocs/v2.php/core/getapppassword");
const QString DEL_APPPASSWORD_ENDPOINT("/ocs/v2.php/core/apppassword");
const QString LOGIN_FLOWV2_ENDPOINT("/index.php/login/v2");
const int LOGIN_FLOWV2_MIN_VERSION = 16;
const int LOGIN_FLOWV2_POLL_INTERVALL = 5000;
class NextcloudApi : public QObject
{
Q_OBJECT
// Generic API properties
Q_PROPERTY(bool verifySsl READ verifySsl WRITE setVerifySsl NOTIFY verifySslChanged) // to allow selfsigned certificates
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // complete API URL = <scheme>://<username>:<password>@<host>[:<port>]/<path>
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged) // url without username and password = <scheme>://<host>[:<port>]/<path>
// the following six properties will update the url and server properties
Q_PROPERTY(QString scheme READ scheme WRITE setScheme NOTIFY schemeChanged)
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
// Networking status information
Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged)
Q_PROPERTY(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
// Nextcloud status (status.php), these properties will be automatically updated on changes of the generic properties
Q_PROPERTY(NextcloudStatus ncStatusStatus READ ncStatusStatus NOTIFY ncStatusStatusChanged)
Q_PROPERTY(bool statusInstalled READ statusInstalled NOTIFY statusInstalledChanged)
Q_PROPERTY(bool statusMaintenance READ statusMaintenance NOTIFY statusMaintenanceChanged)
Q_PROPERTY(bool statusNeedsDbUpgrade READ statusNeedsDbUpgrade NOTIFY statusNeedsDbUpgradeChanged)
Q_PROPERTY(QString statusVersion READ statusVersion NOTIFY statusVersionChanged)
Q_PROPERTY(QString statusVersionString READ statusVersionString NOTIFY statusVersionStringChanged)
Q_PROPERTY(QString statusEdition READ statusEdition NOTIFY statusEditionChanged)
Q_PROPERTY(QString statusProductName READ statusProductName NOTIFY statusProductNameChanged)
Q_PROPERTY(bool statusExtendedSupport READ statusExtendedSupport NOTIFY statusExtendedSupportChanged)
Q_PROPERTY(bool loginFlowV2Possible READ loginFlowV2Possible NOTIFY loginFlowV2PossibleChanged)
// Nextcloud capabilities
Q_PROPERTY(CapabilitiesStatus capabilitiesStatus READ capabilitiesStatus NOTIFY capabilitiesStatusChanged)
// Login status
Q_PROPERTY(LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged)
public:
explicit NextcloudApi(QObject *parent = nullptr);
virtual ~NextcloudApi();
// Status codes
enum NextcloudStatus {
NextcloudUnknown, // Initial unknown state
NextcloudBusy, // Getting information from the nextcloud server
NextcloudSuccess, // Got information about the nextcloud server
NextcloudFailed // Error getting information from the nextcloud server, see ErrorCodes
};
Q_ENUM(NextcloudStatus)
enum CapabilitiesStatus {
CapabilitiesUnknown, // Initial unknown state
CapabilitiesBusy, // Gettin information
CapabilitiesSuccess, // Capabilities successfully read
CapabilitiesFailed // Faild to retreive capabilities
};
Q_ENUM(CapabilitiesStatus)
enum LoginStatus {
LoginUnknown, // Inital unknown state
LoginFlowV2Initiating, // Initiating login flow v2
LoginFlowV2Polling, // Ready for login flow v2
LoginFlowV2Success, // Finished login flow v2
LoginFlowV2Failed, // An error in login flow v2
LoginSuccess, // Login has been verified successfull
LoginFailed // Login has failed, see ErrorCodes
};
Q_ENUM(LoginStatus)
// Generic API properties
bool verifySsl() const { return m_authenticatedRequest.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer; }
void setVerifySsl(bool verify);
QUrl url() const { return m_url; }
void setUrl(QUrl url);
QString server() const;
void setServer(QString server);
QString scheme() const { return m_url.scheme(); }
void setScheme(QString scheme);
QString host() const { return m_url.host(); }
void setHost(QString host);
int port() const { return m_url.port(); }
void setPort(int port);
QString path() const { return m_url.path(); }
void setPath(QString path);
QString username() const { return m_url.userName(); }
void setUsername(QString username);
QString password() const { return m_url.password(); }
void setPassword(QString password);
// Class status information
bool urlValid() const { return m_url.isValid(); }
bool networkAccessible() const { return m_manager.networkAccessible() == QNetworkAccessManager::Accessible; }
bool busy() const;
// Nextcloud status (status.php)
NextcloudStatus ncStatusStatus() const { return m_statusStatus; }
bool statusInstalled() const { return m_status_installed; }
bool statusMaintenance() const { return m_status_maintenance; }
bool statusNeedsDbUpgrade() const { return m_status_needsDbUpgrade; }
QString statusVersion() const { return m_status_version.toString(); }
QString statusVersionString() const { return m_status_versionstring; }
QString statusEdition() const { return m_status_edition; }
QString statusProductName() const { return m_status_productname; }
bool statusExtendedSupport() const { return m_status_extendedSupport; }
bool loginFlowV2Possible() const { return QVersionNumber::fromString(statusVersion()) >= QVersionNumber(LOGIN_FLOWV2_MIN_VERSION); }
// Nextcloud capabilities
CapabilitiesStatus capabilitiesStatus() const { return m_capabilitiesStatus; }
// Login status
LoginStatus loginStatus() const { return m_loginStatus; }
enum ErrorCodes {
NoError,
NoConnectionError,
CommunicationError,
SslHandshakeError,
AuthenticationError
};
Q_ENUM(ErrorCodes)
Q_INVOKABLE const QString errorMessage(int error) const;
public slots:
// Callable functions
Q_INVOKABLE bool getNcStatus();
Q_INVOKABLE bool initiateFlowV2Login();
Q_INVOKABLE void abortFlowV2Login();
Q_INVOKABLE void verifyLogin();
Q_INVOKABLE void getAppPassword();
Q_INVOKABLE void deleteAppPassword();
// API helper functions
Q_INVOKABLE bool get(const QString& endpoint, bool authenticated = true);
Q_INVOKABLE bool post(const QString& endpoint, bool authenticated = true);
Q_INVOKABLE bool put(const QString& endpoint, bool authenticated = true);
Q_INVOKABLE bool del(const QString& endpoint, bool authenticated = true);
signals:
// Generic API properties
void verifySslChanged(bool verify);
void urlChanged(QUrl url);
void serverChanged(QString server);
void schemeChanged(QString scheme);
void hostChanged(QString host);
void portChanged(int port);
void pathChanged(QString path);
void usernameChanged(QString username);
void passwordChanged(QString password);
// Class status information
void urlValidChanged(bool valid);
void networkAccessibleChanged(bool accessible);
void busyChanged(bool busy);
// Nextcloud capabilities
void capabilitiesStatusChanged(CapabilitiesStatus status);
// Nextcloud status (status.php)
void ncStatusStatusChanged(NextcloudStatus status);
void statusInstalledChanged(bool installed);
void statusMaintenanceChanged(bool maintenance);
void statusNeedsDbUpgradeChanged(bool needsDbUpgrade);
void statusVersionChanged(QString version);
void statusVersionStringChanged(QString versionString);
void statusEditionChanged(QString edition);
void statusProductNameChanged(QString productName);
void statusExtendedSupportChanged(bool extendedSupport);
// Login status
void loginStatusChanged(LoginStatus status);
// API helper updates
void getFinished(QNetworkReply& reply);
void postFinished(QNetworkReply& reply);
void putFinished(QNetworkReply& reply);
void delFinished(QNetworkReply& reply);
void apiError(ErrorCodes error);
private slots:
void verifyUrl(QUrl url);
void requireAuthentication(QNetworkReply * reply, QAuthenticator * authenticator);
void onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible);
void replyFinished(QNetworkReply* reply);
void pollLoginUrl();
void sslError(QNetworkReply* reply, const QList<QSslError> &errors);
private:
QUrl m_url;
QNetworkAccessManager m_manager;
QNetworkRequest m_request;
QNetworkRequest m_authenticatedRequest;
// Nextcloud status.php
void updateStatus(const QJsonObject &status);
void setNextcloudStatus(NextcloudStatus status, bool *changed = NULL);
NextcloudStatus m_statusStatus;
bool m_status_installed;
bool m_status_maintenance;
bool m_status_needsDbUpgrade;
QVersionNumber m_status_version;
QString m_status_versionstring;
QString m_status_edition;
QString m_status_productname;
bool m_status_extendedSupport;
// Nextcloud capabilities
bool updateCapabilities(const QJsonObject & capabilities);
void setCababilitiesStatus(CapabilitiesStatus status, bool *changed = NULL);
CapabilitiesStatus m_capabilitiesStatus;
QJsonObject m_capabilities;
// Nextcloud Login Flow v2
// https://docs.nextcloud.com/server/latest/developer_manual/client_apis/LoginFlow/index.html#login-flow-v2
bool updateLoginFlow(const QJsonObject &login);
bool updateLoginCredentials(const QJsonObject &credentials);
void setLoginStatus(LoginStatus status, bool *changed = NULL);
LoginStatus m_loginStatus;
QTimer m_loginPollTimer;
QUrl m_loginUrl;
QUrl m_pollUrl;
QString m_pollToken;
};
#endif // NEXTCLOUDAPI_H

View file

@ -540,6 +540,10 @@
<source>Cleared app data</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Account %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SyntaxPage</name>

View file

@ -540,6 +540,10 @@
<source>Cleared app data</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Account %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SyntaxPage</name>

View file

@ -492,177 +492,182 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="71"/>
<location filename="../qml/pages/SettingsPage.qml" line="58"/>
<source>Account %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="74"/>
<source>Edit</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="77"/>
<location filename="../qml/pages/SettingsPage.qml" line="80"/>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="79"/>
<location filename="../qml/pages/SettingsPage.qml" line="82"/>
<source>Deleting account</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="89"/>
<location filename="../qml/pages/SettingsPage.qml" line="92"/>
<source>Add account</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="97"/>
<location filename="../qml/pages/SettingsPage.qml" line="100"/>
<source>Synchronization</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="101"/>
<location filename="../qml/pages/SettingsPage.qml" line="104"/>
<source>Auto-Sync</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="102"/>
<location filename="../qml/pages/SettingsPage.qml" line="105"/>
<source>Periodically pull notes from the server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="109"/>
<location filename="../qml/pages/SettingsPage.qml" line="112"/>
<source>Disabled</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="109"/>
<location filename="../qml/pages/SettingsPage.qml" line="112"/>
<source>every</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="111"/>
<location filename="../qml/pages/SettingsPage.qml" line="114"/>
<source>Minutes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="112"/>
<location filename="../qml/pages/SettingsPage.qml" line="115"/>
<source>Seconds</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="133"/>
<location filename="../qml/pages/SettingsPage.qml" line="136"/>
<source>The Answer is 42</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="134"/>
<location filename="../qml/pages/SettingsPage.qml" line="137"/>
<source>Congratulation you found the Answer to the Ultimate Question of Life, The Universe, and Everything!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="142"/>
<location filename="../qml/pages/SettingsPage.qml" line="145"/>
<source>Appearance</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="150"/>
<location filename="../qml/pages/SettingsPage.qml" line="153"/>
<source>No sorting</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="174"/>
<location filename="../qml/pages/SettingsPage.qml" line="177"/>
<source>Favorites on top</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="175"/>
<location filename="../qml/pages/SettingsPage.qml" line="178"/>
<source>Show notes marked as favorite above the others</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="213"/>
<location filename="../qml/pages/SettingsPage.qml" line="216"/>
<source>Reset</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="216"/>
<location filename="../qml/pages/SettingsPage.qml" line="219"/>
<source>Reset app settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="218"/>
<location filename="../qml/pages/SettingsPage.qml" line="221"/>
<source>Cleared app data</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="221"/>
<location filename="../qml/pages/SettingsPage.qml" line="224"/>
<source>Resetting the app wipes all application data from the device! This includes offline synced notes, app settings and accounts.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="147"/>
<location filename="../qml/pages/SettingsPage.qml" line="150"/>
<source>Last edited</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="148"/>
<location filename="../qml/pages/SettingsPage.qml" line="151"/>
<source>Category</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="149"/>
<location filename="../qml/pages/SettingsPage.qml" line="152"/>
<source>Title alphabetically</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="152"/>
<location filename="../qml/pages/SettingsPage.qml" line="155"/>
<source>Sort notes by</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="153"/>
<location filename="../qml/pages/SettingsPage.qml" line="156"/>
<source>This will also change how the notes are grouped</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="180"/>
<location filename="../qml/pages/SettingsPage.qml" line="183"/>
<source>Show separator</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="181"/>
<location filename="../qml/pages/SettingsPage.qml" line="184"/>
<source>Show a separator line between the notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="191"/>
<location filename="../qml/pages/SettingsPage.qml" line="194"/>
<source>lines</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="192"/>
<location filename="../qml/pages/SettingsPage.qml" line="195"/>
<source>Number of lines in the preview</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="197"/>
<location filename="../qml/pages/SettingsPage.qml" line="200"/>
<source>Editing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="200"/>
<location filename="../qml/pages/SettingsPage.qml" line="203"/>
<source>Monospaced font</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="201"/>
<location filename="../qml/pages/SettingsPage.qml" line="204"/>
<source>Use a monospeced font to edit a note</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="206"/>
<location filename="../qml/pages/SettingsPage.qml" line="209"/>
<source>Capital &apos;X&apos; in checkboxes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/SettingsPage.qml" line="207"/>
<location filename="../qml/pages/SettingsPage.qml" line="210"/>
<source>For interoperability with other apps such as Joplin</source>
<translation type="unfinished"></translation>
</message>