Used new state info in the Login Dialog

This commit is contained in:
Scharel Clemens 2020-03-26 23:22:45 +01:00
parent 00921391a2
commit 9616dfc6be
9 changed files with 307 additions and 293 deletions

View file

@ -8,13 +8,25 @@ import "pages"
ApplicationWindow
{
id: appWindow
/*
//property NotesApi notesApi
Component.onCompleted: {
notesApi.scheme = account.allowUnecrypted ? "http" : "https"
notesApi.host = account.server
notesApi.username = account.username
notesApi.password = account.password
notesApi.sslVerify = !account.doNotVerifySsl
notesApi.dataFile = appSettings.currentAccount !== "" ? StandardPaths.data + "/" + appSettings.currentAccount + ".json" : ""
}
ConfigurationValue {
id: accounts
key: appSettings.path + "/accountIDs"
defaultValue: [ ]
}
*/
ConfigurationGroup {
id: account
path: "/apps/harbour-nextcloudnotes/accounts/" + appSettings.currentAccount
@ -129,13 +141,16 @@ ApplicationWindow
NotesApi {
id: notesApi
scheme: account.allowUnecrypted ? "http" : "https"
host: account.server
username: account.username
password: account.password
sslVerify: !account.doNotVerifySsl
dataFile: appSettings.currentAccount !== "" ? StandardPaths.data + "/" + appSettings.currentAccount + ".json" : ""
Component.onCompleted: getAllNotes()
Component.onCompleted: {
getAllNotes()
}
onNetworkAccessibleChanged: {
console.log("Device is " + (networkAccessible ? "online" : "offline"))
networkAccessible ? offlineNotification.close(Notification.Closed) : offlineNotification.publish()
@ -152,6 +167,7 @@ ApplicationWindow
}
initialPage: Component { NotesPage { } }
//initialPage: Component { LoginPage { } } // testing
cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: defaultAllowedOrientations
}

View file

@ -1,4 +1,4 @@
import QtQuick 2.2
import QtQuick 2.5
import Sailfish.Silica 1.0
import Nemo.Configuration 1.0
import harbour.nextcloudnotes.notesapi 1.0
@ -8,17 +8,19 @@ Page {
property string accountId
property bool legacyLoginPossible: false
property bool flowLoginV2Possible: false
ConfigurationGroup {
id: account
path: "/apps/harbour-nextcloudnotes/accounts/" + accountId
Component.onCompleted: {
//nameField.text = value("name", "", String)
pageHeader.title = value("name", qsTr("Nextcloud Login"), String)
serverField.text = "https://" + value("server", "", String)
usernameField.text = value("username", "", String)
passwordField.text = value("password", "", String)
unsecureConnectionTextSwitch.checked = value("unsecureConnection", false, Boolean)
unencryptedConnectionTextSwitch.checked = value("unencryptedConnection", false, Boolean)
serverField.text === "https://" ? serverField.focus = true : (usernameField.text === "" ? usernameField.focus = true : (passwordField.text === "" ? passwordField.focus = true : passwordField.focus = false))
}
}
@ -29,59 +31,66 @@ Page {
Connections {
target: notesApi
onStatusStatusChanged: {
switch(notesApi.statusStatus) {
case NotesApi.StatusNone:
console.log("Status: none")
break;
case NotesApi.StatusInitiated:
console.log("Status: initiated")
break;
case NotesApi.StatusBusy:
console.log("Status: busy")
apiProgressBar.label = qsTr("Verifying server address")
break;
case NotesApi.StatusFinished:
console.log("Status: finished")
apiProgressBar.label = qsTr("Server address is valid")
break
case NotesApi.StatusError:
console.log("Status: error")
apiProgressBar.label = qsTr("Please enter a valid server address")
break;
}
console.log(notesApi.statusStatus)
}
onLoginStatusChanged: {
console.log(notesApi.loginStatus)
switch(notesApi.statusStatus) {
case NotesApi.StatusNone:
console.log("Login: none")
break;
case NotesApi.StatusInitiated:
console.log("Login: initiated")
apiProgressBar.label = qsTr("Initiating login")
break;
case NotesApi.StatusBusy:
console.log("Login: busy")
apiProgressBar.label = qsTr("Follow the login procedure in the browser")
break;
case NotesApi.StatusFinished:
console.log("Login: finished")
apiProgressBar.label = qsTr("Login successfull")
break
case NotesApi.StatusError:
console.log("Login: error")
apiProgressBar.label = qsTr("Error while loggin in")
break;
}
}
onStatusInstalledChanged: {
if (notesApi.statusInstalled) {
console.log("Nextcloud instance found")
}
if (notesApi.statusInstalled)
serverField.focus = false
}
onStatusVersionChanged: {
if (notesApi.statusVersion) {
if (notesApi.statusVersion.split('.')[0] >= 16) {
legacyLoginPossible = false
flowLoginV2Possible = true
console.log("Using Flow Login v2")
}
else {
legacyLoginPossible = true
flowLoginV2Possible = false
console.log("Using Legacy Login")
}
}
else {
legacyLoginPossible = false
flowLoginV2Possible = false
}
}
onStatusVersionStringChanged: {
if (notesApi.statusVersionString)
console.log("Nextcloud " + notesApi.statusVersionString)
}
onStatusProductNameChanged: {
if (notesApi.statusProductName) {
pageHeader.title = notesApi.statusProductName
account.setValue("name", notesApi.statusProductName)
}
}
onLoginStatusChanged: {
switch(notesApi.loginStatus) {
case NotesApi.LoginLegacyReady:
apiProgressBar.label = qsTr("Enter your credentials")
break;
//case NotesApi.LoginFlowV2Initiating:
// break;
case NotesApi.LoginFlowV2Polling:
apiProgressBar.label = qsTr("Follow the instructions in the browser")
break;
case NotesApi.LoginFlowV2Success:
notesApi.verifyLogin()
break;
case NotesApi.LoginFlowV2Failed:
apiProgressBar.label = qsTr("Login failed!")
break
case NotesApi.LoginSuccess:
apiProgressBar.label = qsTr("Login successfull!")
account.setValue("username", notesApi.username)
account.setValue("password", notesApi.password)
break;
case NotesApi.LoginFailed:
apiProgressBar.label = qsTr("Login failed!")
break;
default:
apiProgressBar.label = ""
break;
}
}
onLoginUrlChanged: {
if (notesApi.loginUrl) {
@ -92,8 +101,11 @@ Page {
}
}
onServerChanged: {
console.log("Login server: " + notesApi.server)
serverField.text = notesApi.server
if (notesApi.server) {
console.log("Login server: " + notesApi.server)
account.setValue("server", notesApi.server)
serverField.text = notesApi.server
}
}
}
@ -107,7 +119,7 @@ Page {
spacing: Theme.paddingLarge
PageHeader {
title: notesApi.statusProductName ? notesApi.statusProductName : qsTr("Nextcloud Login")
id: pageHeader
}
Image {
@ -122,10 +134,8 @@ Page {
id: apiProgressBar
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
indeterminate: notesApi.statusStatus === NotesApi.StatusInitiated ||
notesApi.statusStatus === NotesApi.StatusBusy ||
notesApi.loginStatus === NotesApi.StatusInitiated ||
notesApi.loginStatus === NotesApi.StatusBusy
indeterminate: notesApi.loginStatus === NotesApi.LoginFlowV2Initiating ||
notesApi.loginStatus === NotesApi.LoginFlowV2Polling
}
Row {
@ -139,17 +149,33 @@ Page {
inputMethodHints: Qt.ImhUrlCharactersOnly
onClicked: if (text === "") text = "https://"
onTextChanged: {
statusBusyIndicatorTimer.restart()
if (acceptableInput)
notesApi.server = text
}
EnterKey.enabled: text.length > 0
EnterKey.iconSource: legacyLoginColumn.visible ? "image://theme/icon-m-enter-next" : "icon-m-enter-accept"
EnterKey.onClicked: legacyLoginColumn.visible ? passwordField.focus = true : (notesApi.loginBusy ? notesApi.abortFlowV2Login() : notesApi.initiateFlowV2Login())
//EnterKey.enabled: text.length > 0
EnterKey.iconSource: legacyLoginPossible ? "image://theme/icon-m-enter-next" : flowLoginV2Possible ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-enter-close"
EnterKey.onClicked: {
if (legacyLoginPossible)
usernameField.focus = true
else if (flowLoginV2Possible && notesApi.loginStatus !== NotesApi.LoginFlowV2Polling)
notesApi.initiateFlowV2Login()
focus = false
}
}
Icon {
id: statusIcon
highlighted: serverField.highlighted
source: notesApi.statusInstalled ? "image://theme/icon-s-accept" : "image://theme/icon-s-decline"
source: notesApi.statusInstalled ? "image://theme/icon-m-acknowledge" : "image://theme/icon-m-question"
BusyIndicator {
anchors.centerIn: parent
size: BusyIndicatorSize.Medium
running: notesApi.ncStatusStatus === NotesApi.NextcloudBusy || (serverField.focus && statusBusyIndicatorTimer.running && !notesApi.statusInstalled)
Timer {
id: statusBusyIndicatorTimer
interval: 200
}
}
}
}
@ -158,18 +184,12 @@ Page {
width: parent.width
spacing: Theme.paddingLarge
visible: opacity !== 0.0
opacity: notesApi.statusStatus === NotesApi.StatusFinished && notesApi.statusVersion.split('.')[0] >= 16 ? 1.0 : 0.0
opacity: flowLoginV2Possible ? 1.0 : 0.0
Behavior on opacity { FadeAnimator {} }
Label {
text: "Flow Login v2"
x: Theme.horizontalPageMargin
}
Button {
id: loginButton
anchors.horizontalCenter: parent.horizontalCenter
property bool pushed: false
text: notesApi.loginBusy ? qsTr("Abort") : qsTr("Login")
onClicked: notesApi.loginBusy ? notesApi.abortFlowV2Login() : notesApi.initiateFlowV2Login()
text: notesApi.loginStatus === NotesApi.LoginFlowV2Polling ? qsTr("Abort") : qsTr("Login")
onClicked: notesApi.loginStatus === NotesApi.LoginFlowV2Polling ? notesApi.abortFlowV2Login() : notesApi.initiateFlowV2Login()
}
}
@ -177,16 +197,11 @@ Page {
id: legacyLoginColumn
width: parent.width
visible: opacity !== 0.0
opacity: notesApi.statusStatus === NotesApi.StatusFinished && notesApi.statusVersion.split('.')[0] < 16 ? 1.0 : 0.0
opacity: legacyLoginPossible ? 1.0 : 0.0
Behavior on opacity { FadeAnimator {} }
Label {
text: "Legacy Login"
x: Theme.horizontalPageMargin
}
TextField {
id: usernameField
width: parent.width
//text: account.value("name", "", String)
placeholderText: qsTr("Username")
label: placeholderText
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
@ -198,50 +213,20 @@ Page {
PasswordField {
id: passwordField
width: parent.width
//text: account.value("password", "", String)
placeholderText: qsTr("Password")
label: placeholderText
errorHighlight: text.length === 0// && focus === true
EnterKey.enabled: text.length > 0
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
EnterKey.onClicked: loginDialog.accept()
EnterKey.onClicked: notesApi.verifyLogin(usernameField.text, passwordField.text)
}
Button {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Test Login")
onClicked: notesApi.verifyLogin(usernameField.text, passwordField.text)
}
}
/*
TextField {
id: nameField
width: parent.width
//text: account.value("name", "", String)
text: notesApi.statusProductName
enabled: false
placeholderText: qsTr("Account name")
label: placeholderText
errorHighlight: text.length === 0// && focus === true
EnterKey.enabled: text.length > 0
EnterKey.iconSource: "image://theme/icon-m-enter-next"
EnterKey.onClicked: serverField.focus = true
}
TextField {
id: serverField
// regExp combined from https://stackoverflow.com/a/3809435 (EDIT: removed ? after https to force SSL) and https://www.regextester.com/22
//property var encryptedRegEx: /^https:\/\/(((www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))))([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/
//property var unencryptedRegEx : /^https?:\/\/(((www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))))([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/
width: parent.width
//text: account.value("server", "https://", String)
placeholderText: qsTr("Nextcloud server")
label: placeholderText// + " " + qsTr("(starting with \"https://\")")
inputMethodHints: Qt.ImhUrlCharactersOnly
//validator: RegExpValidator { regExp: unencryptedConnectionTextSwitch.checked ? serverField.unencryptedRegEx : serverField.encryptedRegEx }
errorHighlight: !acceptableInput// && focus === true
EnterKey.enabled: acceptableInput
EnterKey.iconSource: "image://theme/icon-m-enter-next"
EnterKey.onClicked: usernameField.focus = true
onTextChanged: notesApi.host = text
}
*/
SectionHeader {
text: qsTr("Security")
}
@ -256,14 +241,12 @@ Page {
id: unsecureConnectionTextSwitch
text: qsTr("Do not check certificates")
description: qsTr("Enable this option to allow selfsigned certificates")
//checked: account.value("allowUnencryptedConnection", false, Boolean)
}
TextSwitch {
id: unencryptedConnectionTextSwitch
automaticCheck: false
text: qsTr("Allow unencrypted connections")
description: qsTr("")
//checked: account.value("unencryptedConnection", false, Boolean)
onClicked: {
if (checked) {
checked = false

View file

@ -2,7 +2,7 @@ import QtQuick 2.2
import Sailfish.Silica 1.0
import Nemo.Configuration 1.0
import Nemo.Notifications 1.0
import harbour.nextcloudnotes.notesmodel 1.0
//import harbour.nextcloudnotes.notesmodel 1.0
Page {
id: page

View file

@ -16,12 +16,16 @@ int main(int argc, char *argv[])
app->setOrganizationName("harbour-nextcloudnotes");
qDebug() << app->applicationDisplayName() << app->applicationVersion();
qmlRegisterType<NotesApi>("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi");
qmlRegisterType<NotesProxyModel>("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel");
NotesApi notesApi;
QQuickView* view = SailfishApp::createView();
//view->engine()->rootContext()->setContextProperty("notesApi", &notesApi);
//view->engine()->rootContext()->setContextProperty("notesModel", notesApi.model());
view->setSource(SailfishApp::pathTo("qml/harbour-nextcloudnotes.qml"));
#ifdef QT_DEBUG
view->rootContext()->setContextProperty("debug", QVariant(true));
#else

View file

@ -4,8 +4,8 @@
#include <QJsonDocument>
#include <QJsonObject>
NotesApi::NotesApi(const QString statusEndpoint, const QString loginEndpoint, const QString notesEndpoint, QObject *parent)
: QObject(parent), m_statusEndpoint(statusEndpoint), m_loginEndpoint(loginEndpoint), m_notesEndpoint(notesEndpoint)
NotesApi::NotesApi(const QString statusEndpoint, const QString loginEndpoint, const QString ocsEndpoint, const QString notesEndpoint, QObject *parent)
: QObject(parent), m_statusEndpoint(statusEndpoint), m_loginEndpoint(loginEndpoint), m_ocsEndpoint(ocsEndpoint), m_notesEndpoint(notesEndpoint)
{
// TODO verify connections (also in destructor)
m_loginPollTimer.setInterval(POLL_INTERVALL);
@ -16,6 +16,10 @@ NotesApi::NotesApi(const QString statusEndpoint, const QString loginEndpoint, co
setNcStatusStatus(NextcloudStatus::NextcloudUnknown);
setLoginStatus(LoginStatus::LoginUnknown);
m_ncStatusStatus = NextcloudStatus::NextcloudUnknown;
m_status_installed = false;
m_status_maintenance = false;
m_status_needsDbUpgrade = false;
m_status_extendedSupport = false;
m_loginStatus = LoginStatus::LoginUnknown;
mp_model = new NotesModel(this);
mp_modelProxy = new NotesProxyModel(this);
@ -86,8 +90,7 @@ QString NotesApi::server() const {
}
void NotesApi::setServer(QString serverUrl) {
QUrl url(serverUrl);
qDebug() << serverUrl << server();
QUrl url(serverUrl.trimmed());
if (serverUrl != server()) {
setScheme(url.scheme());
setHost(url.host());
@ -100,6 +103,7 @@ void NotesApi::setScheme(QString scheme) {
if (scheme != m_url.scheme() && (scheme == "http" || scheme == "https")) {
m_url.setScheme(scheme);
emit schemeChanged(m_url.scheme());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
@ -108,6 +112,7 @@ void NotesApi::setHost(QString host) {
if (host != m_url.host()) {
m_url.setHost(host);
emit hostChanged(m_url.host());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
@ -116,6 +121,7 @@ void NotesApi::setPort(int port) {
if (port != m_url.port() && port >= 1 && port <= 65535) {
m_url.setPort(port);
emit portChanged(m_url.port());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
@ -148,11 +154,12 @@ void NotesApi::setPath(QString path) {
if (path != m_url.path()) {
m_url.setPath(path);
emit pathChanged(m_url.path());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
void NotesApi::setDataFile(QString dataFile) {
void NotesApi::setDataFile(const QString &dataFile) {
if (dataFile != m_jsonFile.fileName()) {
m_jsonFile.close();
m_jsonFile.setFileName(dataFile);
@ -202,10 +209,6 @@ void NotesApi::abortFlowV2Login() {
emit loginUrlChanged(m_loginUrl);
m_pollUrl.clear();
m_pollToken.clear();
if (m_loginReply->isRunning())
m_loginReply->abort();
if (m_pollReply->isRunning())
m_pollReply->abort();
setLoginStatus(LoginStatus::LoginUnknown);
}
@ -222,6 +225,22 @@ void NotesApi::pollLoginUrl() {
}
}
void NotesApi::verifyLogin(QString username, QString password) {
m_ocsRequest = m_authenticatedRequest;
if (username.isEmpty())
username = this->username();
if (password.isEmpty())
password = this->password();
QUrl url = apiEndpointUrl(m_ocsEndpoint + QString("/users/%1").arg(username));
m_ocsRequest.setRawHeader("Authorization", "Basic " + QString(username + ":" + password).toLocal8Bit().toBase64());
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "GET" << url.toDisplayString();
m_ocsRequest.setUrl(url);
m_ocsReply = m_manager.get(m_ocsRequest);
emit busyChanged(true);
}
}
void NotesApi::getAllNotes(QStringList excludeFields) {
QUrl url = apiEndpointUrl(m_notesEndpoint + "/notes");
if (!excludeFields.isEmpty())
@ -347,54 +366,56 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
emit error(NoError);
QByteArray data = reply->readAll();
QJsonDocument json = QJsonDocument::fromJson(data);
/*if (reply->url().toString().contains(m_loginEndpoint)) {
qDebug() << "Login reply";
}
else if (reply->url() == m_pollUrl) {
qDebug() << "Poll reply";
}
else if (reply->url().toString().contains(m_statusEndpoint)) {
qDebug() << "Status reply";
}
else if (reply->url().toString().contains(m_notesEndpoint)) {
qDebug() << "Notes reply";
}*/
if (reply == m_loginReply) {
qDebug() << "Login reply";
if (json.isObject())
updateLoginFlow(json.object());
m_loginReply = NULL;
}
else if (reply == m_pollReply) {
qDebug() << "Poll reply, finished";
if (json.isObject())
updateLoginCredentials(json.object());
m_pollReply = NULL;
abortFlowV2Login();
}
else if (reply == m_statusReply) {
qDebug() << "Status reply";
if (json.isObject())
updateNcStatus(json.object());
m_statusReply = NULL;
}
else if (m_notesReplies.contains(reply)) {
qDebug() << "Notes reply";
if (mp_model) {
if (mp_model->fromJsonDocument(json)) {
m_lastSync = QDateTime::currentDateTimeUtc();
emit lastSyncChanged(m_lastSync);
}
if (reply == m_ocsReply) {
qDebug() << "OCS reply";
QString xml(data);
if (xml.contains("<status>ok</status>")) {
qDebug() << "Login Success!";
setLoginStatus(LoginSuccess);
}
else {
qDebug() << "Login Failed!";
setLoginStatus(LoginFailed);
}
m_notesReplies.removeOne(reply);
emit busyChanged(busy());
}
else {
qDebug() << "Unknown reply";
QJsonDocument json = QJsonDocument::fromJson(data);
if (reply == m_loginReply) {
qDebug() << "Login reply";
if (json.isObject())
updateLoginFlow(json.object());
m_loginReply = NULL;
}
else if (reply == m_pollReply) {
qDebug() << "Poll reply, finished";
if (json.isObject())
updateLoginCredentials(json.object());
m_pollReply = NULL;
abortFlowV2Login();
}
else if (reply == m_statusReply) {
qDebug() << "Status reply";
if (json.isObject())
updateNcStatus(json.object());
m_statusReply = NULL;
}
else if (m_notesReplies.contains(reply)) {
qDebug() << "Notes reply";
if (mp_model) {
if (mp_model->fromJsonDocument(json)) {
m_lastSync = QDateTime::currentDateTimeUtc();
emit lastSyncChanged(m_lastSync);
}
}
m_notesReplies.removeOne(reply);
emit busyChanged(busy());
}
else {
qDebug() << "Unknown reply";
}
//qDebug() << data;
}
//qDebug() << data;
}
else if (reply->error() == QNetworkReply::AuthenticationRequiredError) {
emit error(AuthenticationError);
@ -494,8 +515,9 @@ void NotesApi::updateNcStatus(const QJsonObject &status) {
}
void NotesApi::setNcStatusStatus(NextcloudStatus status, bool *changed) {
*changed = status != m_ncStatusStatus;
if (*changed) {
if (status != m_ncStatusStatus) {
if (changed)
*changed = true;
m_ncStatusStatus = status;
emit ncStatusStatusChanged(m_ncStatusStatus);
}
@ -558,8 +580,9 @@ bool NotesApi::updateLoginCredentials(const QJsonObject &credentials) {
}
void NotesApi::setLoginStatus(LoginStatus status, bool *changed) {
*changed = status != m_loginStatus;
if (*changed) {
if (status != m_loginStatus) {
if (changed)
*changed = true;
m_loginStatus = status;
emit loginStatusChanged(m_loginStatus);
}

View file

@ -13,68 +13,85 @@
#define STATUS_ENDPOINT "/status.php"
#define LOGIN_ENDPOINT "/index.php/login/v2"
#define NOTES_ENDPOINT "/index.php/apps/notes/api/v0.2"
#define OCS_ENDPOINT "/ocs/v1.php/cloud"
#define POLL_INTERVALL 5000
class NotesApi : public QObject
{
Q_OBJECT
Q_PROPERTY(bool sslVerify READ sslVerify WRITE setSslVerify NOTIFY sslVerifyChanged)
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged)
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
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 username READ username WRITE setUsername NOTIFY usernameChanged)
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QString dataFile READ dataFile WRITE setDataFile NOTIFY dataFileChanged)
Q_PROPERTY(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged)
Q_PROPERTY(QDateTime lastSync READ lastSync NOTIFY lastSyncChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
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(LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged)
Q_PROPERTY(QUrl loginUrl READ loginUrl NOTIFY loginUrlChanged)
public:
explicit NotesApi(const QString statusEndpoint = STATUS_ENDPOINT,
const QString loginEndpoint = LOGIN_ENDPOINT,
const QString ocsEndpoint = OCS_ENDPOINT,
const QString notesEndpoint = NOTES_ENDPOINT,
QObject *parent = nullptr);
QObject *parent = NULL);
virtual ~NotesApi();
Q_PROPERTY(bool sslVerify READ sslVerify WRITE setSslVerify NOTIFY sslVerifyChanged)
bool sslVerify() const { return m_authenticatedRequest.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer; }
void setSslVerify(bool verify);
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
QUrl url() const { return m_url; }
void setUrl(QUrl url);
Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged)
bool urlValid() const { return m_url.isValid(); }
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
QString server() const;
void setServer(QString server);
Q_PROPERTY(QString scheme READ scheme WRITE setScheme NOTIFY schemeChanged)
QString scheme() const { return m_url.scheme(); }
void setScheme(QString scheme);
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
QString host() const { return m_url.host(); }
void setHost(QString host);
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
int port() const { return m_url.port(); }
void setPort(int port);
Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
QString username() const { return m_url.userName(); }
void setUsername(QString username);
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
QString password() const { return m_url.password(); }
void setPassword(QString password);
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
QString path() const { return m_url.path(); }
void setPath(QString path);
Q_PROPERTY(QString dataFile READ dataFile WRITE setDataFile NOTIFY dataFileChanged)
QString dataFile() const { return m_jsonFile.fileName(); }
void setDataFile(QString dataFile);
void setDataFile(const QString &dataFile);
Q_PROPERTY(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged)
bool networkAccessible() const { return m_manager.networkAccessible() == QNetworkAccessManager::Accessible; }
Q_PROPERTY(QDateTime lastSync READ lastSync NOTIFY lastSyncChanged)
QDateTime lastSync() const { return m_lastSync; }
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
bool busy() const { return !m_notesReplies.empty();; }
enum NextcloudStatus {
@ -96,33 +113,23 @@ public:
};
Q_ENUM(LoginStatus)
Q_PROPERTY(NextcloudStatus ncStatusStatus READ ncStatusStatus NOTIFY ncStatusStatusChanged)
NextcloudStatus ncStatusStatus() const { return m_ncStatusStatus; }
Q_PROPERTY(bool statusInstalled READ statusInstalled NOTIFY statusInstalledChanged)
bool statusInstalled() const { return m_status_installed; }
Q_PROPERTY(bool statusMaintenance READ statusMaintenance NOTIFY statusMaintenanceChanged)
bool statusMaintenance() const { return m_status_maintenance; }
Q_PROPERTY(bool statusNeedsDbUpgrade READ statusNeedsDbUpgrade NOTIFY statusNeedsDbUpgradeChanged)
bool statusNeedsDbUpgrade() const { return m_status_needsDbUpgrade; }
Q_PROPERTY(QString statusVersion READ statusVersion NOTIFY statusVersionChanged)
QString statusVersion() const { return m_status_version; }
Q_PROPERTY(QString statusVersionString READ statusVersionString NOTIFY statusVersionStringChanged)
QString statusVersionString() const { return m_status_versionstring; }
Q_PROPERTY(QString statusEdition READ statusEdition NOTIFY statusEditionChanged)
QString statusEdition() const { return m_status_edition; }
Q_PROPERTY(QString statusProductName READ statusProductName NOTIFY statusProductNameChanged)
QString statusProductName() const { return m_status_productname; }
Q_PROPERTY(bool statusExtendedSupport READ statusExtendedSupport NOTIFY statusExtendedSupportChanged)
bool statusExtendedSupport() const { return m_status_extendedSupport; }
Q_PROPERTY(LoginStatus loginStatus READ loginStatus NOTIFY loginStatusChanged)
LoginStatus loginStatus() const { return m_loginStatus; }
Q_PROPERTY(QUrl loginUrl READ loginUrl NOTIFY loginUrlChanged)
QUrl loginUrl() const { return m_loginUrl; }
Q_INVOKABLE bool getNcStatus();
Q_INVOKABLE bool initiateFlowV2Login();
Q_INVOKABLE void abortFlowV2Login();
Q_INVOKABLE void verifyLogin(QString username = QString(), QString password = QString());
Q_INVOKABLE void getAllNotes(QStringList excludeFields = QStringList());
Q_INVOKABLE void getNote(double noteId, QStringList excludeFields = QStringList());
Q_INVOKABLE void createNote(QVariantMap fields = QVariantMap());
@ -139,6 +146,7 @@ public:
SslHandshakeError,
AuthenticationError
};
Q_ENUM(ErrorCodes)
Q_INVOKABLE const QString errorMessage(ErrorCodes error) const;
signals:
@ -187,6 +195,7 @@ private:
QNetworkAccessManager m_manager;
QNetworkRequest m_request;
QNetworkRequest m_authenticatedRequest;
QNetworkRequest m_ocsRequest;
QFile m_jsonFile;
NotesModel* mp_model;
NotesProxyModel* mp_modelProxy;
@ -220,6 +229,10 @@ private:
QUrl m_pollUrl;
QString m_pollToken;
// Nextcloud OCS API - https://docs.nextcloud.com/server/18/developer_manual/client_apis/OCS/ocs-api-overview.html
const QString m_ocsEndpoint;
QNetworkReply* m_ocsReply;
// Nextcloud Notes API - https://github.com/nextcloud/notes/wiki/Notes-0.2
const QString m_notesEndpoint;
QVector<QNetworkReply*> m_notesReplies;

View file

@ -159,31 +159,23 @@
<translation type="unfinished"></translation>
</message>
<message>
<source>Verifying server address</source>
<source>Follow the instructions in the browser</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Server address is valid</source>
<source>Login successfull!</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Initiating login</source>
<source>Login failed!</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Follow the login procedure in the browser</source>
<source>Enter your credentials</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Login successfull</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Error while loggin in</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Please enter a valid server address</source>
<source>Test Login</source>
<translation type="unfinished"></translation>
</message>
</context>
@ -677,14 +669,14 @@ You can also use other markdown syntax inside them.</source>
</context>
<context>
<name>harbour-nextcloudnotes</name>
<message>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Notes</source>
<translation type="unfinished">Notizen</translation>
</message>
<message>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Synced</source>
<translation type="unfinished"></translation>

View file

@ -159,31 +159,23 @@
<translation type="unfinished"></translation>
</message>
<message>
<source>Verifying server address</source>
<source>Follow the instructions in the browser</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Server address is valid</source>
<source>Login successfull!</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Initiating login</source>
<source>Login failed!</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Follow the login procedure in the browser</source>
<source>Enter your credentials</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Login successfull</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Error while loggin in</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Please enter a valid server address</source>
<source>Test Login</source>
<translation type="unfinished"></translation>
</message>
</context>
@ -677,14 +669,14 @@ You can also use other markdown syntax inside them.</source>
</context>
<context>
<name>harbour-nextcloudnotes</name>
<message>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Notes</source>
<translation type="unfinished">Anteckningar</translation>
</message>
<message>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Synced</source>
<translation type="unfinished"></translation>

View file

@ -133,92 +133,83 @@
<context>
<name>LoginPage</name>
<message>
<location filename="../qml/pages/LoginPage.qml" line="42"/>
<source>Verifying server address</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="46"/>
<source>Server address is valid</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="63"/>
<source>Initiating login</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="67"/>
<source>Follow the login procedure in the browser</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="71"/>
<source>Login successfull</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="75"/>
<source>Error while loggin in</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="110"/>
<location filename="../qml/pages/LoginPage.qml" line="18"/>
<source>Nextcloud Login</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="136"/>
<location filename="../qml/pages/LoginPage.qml" line="146"/>
<source>Nextcloud server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="190"/>
<location filename="../qml/pages/LoginPage.qml" line="205"/>
<source>Username</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="202"/>
<location filename="../qml/pages/LoginPage.qml" line="216"/>
<source>Password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="171"/>
<location filename="../qml/pages/LoginPage.qml" line="191"/>
<source>Abort</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="50"/>
<source>Please enter a valid server address</source>
<location filename="../qml/pages/LoginPage.qml" line="74"/>
<source>Follow the instructions in the browser</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="171"/>
<location filename="../qml/pages/LoginPage.qml" line="83"/>
<source>Login successfull!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="80"/>
<location filename="../qml/pages/LoginPage.qml" line="88"/>
<source>Login failed!</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="69"/>
<source>Enter your credentials</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="191"/>
<source>Login</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="246"/>
<location filename="../qml/pages/LoginPage.qml" line="225"/>
<source>Test Login</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="231"/>
<source>Security</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="253"/>
<location filename="../qml/pages/LoginPage.qml" line="238"/>
<source>&lt;strong&gt;CAUTION: Your password will be saved without any encryption on the device!&lt;/strong&gt;&lt;br&gt;Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to &lt;i&gt;Settings&lt;/i&gt; &lt;i&gt;Security&lt;/i&gt;.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="257"/>
<location filename="../qml/pages/LoginPage.qml" line="242"/>
<source>Do not check certificates</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="258"/>
<location filename="../qml/pages/LoginPage.qml" line="243"/>
<source>Enable this option to allow selfsigned certificates</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginPage.qml" line="264"/>
<location filename="../qml/pages/LoginPage.qml" line="248"/>
<source>Allow unencrypted connections</source>
<translation type="unfinished"></translation>
</message>
@ -305,37 +296,37 @@
<context>
<name>NotesApi</name>
<message>
<location filename="../src/notesapi.cpp" line="521"/>
<location filename="../src/notesapi.cpp" line="319"/>
<source>No network connection available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="524"/>
<location filename="../src/notesapi.cpp" line="322"/>
<source>Failed to communicate with the Nextcloud server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="527"/>
<location filename="../src/notesapi.cpp" line="325"/>
<source>An error happened while reading from the local storage</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="530"/>
<location filename="../src/notesapi.cpp" line="328"/>
<source>An error happened while writing to the local storage</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="533"/>
<location filename="../src/notesapi.cpp" line="331"/>
<source>An error occured while establishing an encrypted connection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="536"/>
<location filename="../src/notesapi.cpp" line="334"/>
<source>Could not authenticate to the Nextcloud instance</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="539"/>
<location filename="../src/notesapi.cpp" line="337"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
@ -833,22 +824,22 @@ You can also use other markdown syntax inside them.</source>
<context>
<name>harbour-nextcloudnotes</name>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="96"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="108"/>
<source>Notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="97"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="109"/>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="98"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="110"/>
<source>Synced</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="105"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="117"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>