From a026c2ee8d7ee214899e499887b2ea1ef3cd734a Mon Sep 17 00:00:00 2001 From: Scharel Clemens Date: Sun, 10 Nov 2019 03:35:33 +0100 Subject: [PATCH] Implemented the API in C++. Still plenty bugs. --- harbour-nextcloudnotes.pro | 5 +- qml/components/NoteDelegateModel.qml | 243 ---------------------- qml/harbour-nextcloudnotes.qml | 38 +++- qml/pages/LoginDialog.qml | 10 +- qml/pages/NotesPage.qml | 6 +- rpm/harbour-nextcloudnotes.spec | 2 +- src/harbour-nextcloudnotes.cpp | 2 + src/note.cpp | 86 +++++++- src/note.h | 76 ++----- src/notesapi.cpp | 212 +++++++++++++++++++ src/notesapi.h | 96 +++++++++ src/notesmodel.cpp | 18 +- src/notesmodel.h | 1 + src/sslconfiguration.cpp | 2 +- translations/harbour-nextcloudnotes-de.ts | 75 ++----- translations/harbour-nextcloudnotes-sv.ts | 69 +----- translations/harbour-nextcloudnotes.ts | 62 +----- 17 files changed, 488 insertions(+), 515 deletions(-) delete mode 100644 qml/components/NoteDelegateModel.qml create mode 100644 src/notesapi.cpp create mode 100644 src/notesapi.h diff --git a/harbour-nextcloudnotes.pro b/harbour-nextcloudnotes.pro index a2dae15..7a7f476 100644 --- a/harbour-nextcloudnotes.pro +++ b/harbour-nextcloudnotes.pro @@ -17,11 +17,13 @@ CONFIG += sailfishapp DEFINES += APP_VERSION=\\\"$$VERSION\\\" HEADERS += \ + src/notesapi.h \ src/sslconfiguration.h \ src/notesmodel.h \ src/note.h SOURCES += src/harbour-nextcloudnotes.cpp \ + src/notesapi.cpp \ src/sslconfiguration.cpp \ src/notesmodel.cpp \ src/note.cpp @@ -45,8 +47,7 @@ DISTFILES += qml/harbour-nextcloudnotes.qml \ qml/pages/MITLicense.qml \ qml/pages/GPLLicense.qml \ qml/pages/SyntaxPage.qml \ - qml/components/NotesApi.qml \ - qml/components/NoteDelegateModel.qml + qml/components/NotesApi.qml SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 diff --git a/qml/components/NoteDelegateModel.qml b/qml/components/NoteDelegateModel.qml deleted file mode 100644 index 86695e9..0000000 --- a/qml/components/NoteDelegateModel.qml +++ /dev/null @@ -1,243 +0,0 @@ -import QtQuick 2.5 -import Sailfish.Silica 1.0 -import QtQml.Models 2.2 - -DelegateModel { - id: noteListModel - property string searchText: "" - property bool favoritesOnTop - property string sortBy - property bool showSeparator - property int previewLineCount - - onSearchTextChanged: reload() - onSortByChanged: reload() - Connections { - target: api - onModelChanged: { - console.log("API model changed!") - reload() - } - onNoteCreated: { - console.log("New note created: " + id) - } - onNoteRemoved: { - console.log("Note removed: " + id) - } - onNoteChanged: { - console.log("Note changed: " + id) - } - } - - function reload() { - if (items.count > 0) - items.setGroups(0, items.count, "unsorted") - if (searchItems.count > 0) - searchItems.setGroups(0, searchItems.count, "unsorted") - } - - items.includeByDefault: false - groups: [ - DelegateModelGroup { - id: searchItems - name: "search" - }, - DelegateModelGroup { - id: unsortedItems - name: "unsorted" - includeByDefault: true - onChanged: { - switch(sortBy) { - case "date": - noteListModel.sort(function(left, right) { - if (favoritesOnTop) { - if (left.favorite === right.favorite) - return left.modified > right.modified - else - return left.favorite - } - else - return left.modified > right.modified - }) - break - case "category": - noteListModel.sort(function(left, right) { - if (favoritesOnTop) { - if (left.favorite === right.favorite) - return left.category < right.category - else - return left.favorite - } - else - return left.category < right.category - }) - break - case "title": - noteListModel.sort(function(left, right) { - if (favoritesOnTop) { - if (left.favorite === right.favorite) - return left.title < right.title - else - return left.favorite - } - else - return left.title < right.title - }) - break - default: - setGroups(0, unsortedItems.count, "items") - break - } - } - } - ] - Connections { - target: items - onCountChanged: console.log(count) - } - - function insertPosition(lessThan, item) { - var lower = 0 - var upper = items.count - while (lower < upper) { - var middle = Math.floor(lower + (upper - lower) / 2) - var result = lessThan(item.model, items.get(middle).model); - if (result) { - upper = middle - } else { - lower = middle + 1 - } - } - return lower - } - function sort(lessThan) { - while (unsortedItems.count > 0) { - var item = unsortedItems.get(0) - var index = insertPosition(lessThan, item) - - if (searchText === "" || - item.model.title.toLowerCase().indexOf(searchText.toLowerCase()) >= 0 || - item.model.content.toLowerCase().indexOf(searchText.toLowerCase()) >= 0 || - item.model.category.toLowerCase().indexOf(searchText.toLowerCase()) >= 0) { - //console.log("Adding " + item.model.title + " to model") - item.groups = "items" - items.move(item.itemsIndex, index) - } - else if (searchText !== "") { - item.groups = "search" - } - } - } - - delegate: BackgroundItem { - id: note - - contentHeight: titleLabel.height + previewLabel.height + 2*Theme.paddingSmall - height: contentHeight + menu.height - width: parent.width - highlighted: down || menu.active - /*ListView.onAdd: AddAnimation { - target: note //searchText !== "" ? null : note - } - ListView.onRemove: RemoveAnimation { - target: note //searchText !== "" ? null : note - }*/ - RemorseItem { - id: remorse - } - - onClicked: pageStack.push(Qt.resolvedUrl("../pages/NotePage.qml"), - { note: api.model.get(index) }) - onPressAndHold: menu.open(note) - - Separator { - width: parent.width - color: Theme.primaryColor - anchors.top: titleLabel.top - visible: showSeparator && index !== 0 - } - - IconButton { - id: isFavoriteIcon - anchors.left: parent.left - anchors.top: parent.top - icon.source: (favorite ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") + - (note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor) - onClicked: { - api.updateNote(id, {'favorite': !favorite} ) - } - } - - Label { - id: titleLabel - anchors.left: isFavoriteIcon.right - anchors.leftMargin: Theme.paddingSmall - anchors.right: categoryRectangle.visible ? categoryRectangle.left : parent.right - anchors.top: parent.top - text: title - truncationMode: TruncationMode.Fade - color: note.highlighted ? Theme.highlightColor : Theme.primaryColor - } - - Rectangle { - id: categoryRectangle - anchors.right: parent.right - anchors.rightMargin: Theme.horizontalPageMargin - anchors.top: parent.top - anchors.topMargin: Theme.paddingSmall - width: categoryLabel.width + Theme.paddingLarge - height: categoryLabel.height + Theme.paddingSmall - color: "transparent" - border.color: Theme.highlightColor - radius: height / 4 - visible: sortBy !== "category" && categoryLabel.text.length > 0 - Label { - id: categoryLabel - anchors.centerIn: parent - text: category - color: note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor - font.pixelSize: Theme.fontSizeExtraSmall - } - } - - Label { - id: previewLabel - anchors.left: isFavoriteIcon.right - anchors.leftMargin: Theme.paddingSmall - anchors.right: parent.right - anchors.rightMargin: Theme.horizontalPageMargin - anchors.top: titleLabel.bottom - text: parseText(content) - font.pixelSize: Theme.fontSizeExtraSmall - textFormat: Text.PlainText - wrapMode: Text.Wrap - elide: Text.ElideRight - maximumLineCount: previewLineCount > 0 ? previewLineCount : 1 - visible: previewLineCount > 0 - color: note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor - function parseText (preText) { - var lines = preText.split('\n') - lines.splice(0,1); - var newText = lines.join('\n'); - return newText.replace(/^\s*$(?:\r\n?|\n)/gm, "") - } - } - - ContextMenu { - id: menu - MenuLabel { - id: modifiedLabel - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Modified") + ": " + new Date(modified * 1000).toLocaleString(Qt.locale(), Locale.ShortFormat) - } - MenuItem { - text: qsTr("Delete") - onClicked: { - remorse.execute(note, qsTr("Deleting note"), function() { - api.deleteNote(id) - }) - } - } - } - } -} diff --git a/qml/harbour-nextcloudnotes.qml b/qml/harbour-nextcloudnotes.qml index d8f8485..c6fb5a7 100644 --- a/qml/harbour-nextcloudnotes.qml +++ b/qml/harbour-nextcloudnotes.qml @@ -1,10 +1,11 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import Nemo.Configuration 1.0 +import harbour.nextcloudnotes.notesapi 1.0 import harbour.nextcloudnotes.notesmodel 1.0 import harbour.nextcloudnotes.sslconfiguration 1.0 import "pages" -import "components" +//import "components" ApplicationWindow { @@ -47,8 +48,9 @@ ApplicationWindow property bool useCapitalX: value("useCapitalX", false, Boolean) onCurrentAccountChanged: { account.path = "/apps/harbour-nextcloudnotes/accounts/" + currentAccount - noteListModel.clear() - api.getNotesFromApi() + //noteListModel.clear() + //api.getNotesFromApi() + api.getAllNotes(); } function addAccount() { @@ -86,10 +88,10 @@ ApplicationWindow } } - SslConfiguration { + /*SslConfiguration { id: ssl checkCert: !account.unsecureConnection - } + }*/ Timer { id: autoSyncTimer @@ -99,7 +101,8 @@ ApplicationWindow triggeredOnStart: true onTriggered: { if (!api.busy) { - api.getNotesFromApi() + //api.getNotesFromApi() + api.getAllNotes(); } else { triggeredOnStart = false @@ -115,15 +118,32 @@ ApplicationWindow } NotesApi { + id: api + /*scheme: "https" + host: account.server + path: "/index.php/apps/notes/api/" + account.version + username: account.username + password: account.password*/ + } + + Component.onCompleted: { + api.scheme = "https" + api.host = account.server + api.path = "/index.php/apps/notes/api/" + account.version + api.username = account.username + api.password = account.password + } + + /*NotesApi { id: api onResponseChanged: noteListModel.applyJSON(response) } - NotesModel { + /*NotesModel { id: noteListModel - sortBy: appSettings.sortBy + sortBy: appSettisignangs.sortBy favoritesOnTop: appSettings.favoritesOnTop - } + }*/ initialPage: Component { NotesPage { } } cover: Qt.resolvedUrl("cover/CoverPage.qml") diff --git a/qml/pages/LoginDialog.qml b/qml/pages/LoginDialog.qml index 220be19..c52e1fe 100644 --- a/qml/pages/LoginDialog.qml +++ b/qml/pages/LoginDialog.qml @@ -13,7 +13,7 @@ Dialog { path: "/apps/harbour-nextcloudnotes/accounts/" + accountId Component.onCompleted: { nameField.text = value("name", "", String) - serverField.text = value("server", "https://", String) + serverField.text = value("server", "", String) usernameField.text = value("username", "", String) passwordField.text = value("password", "", String) unsecureConnectionTextSwitch.checked = value("unsecureConnection", false, Boolean) @@ -72,14 +72,14 @@ Dialog { 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@:%_\+.~#?&//=]*)$/ + //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://\")") + label: placeholderText// + " " + qsTr("(starting with \"https://\")") inputMethodHints: Qt.ImhUrlCharactersOnly - validator: RegExpValidator { regExp: unencryptedConnectionTextSwitch.checked ? serverField.unencryptedRegEx : serverField.encryptedRegEx } + //validator: RegExpValidator { regExp: unencryptedConnectionTextSwitch.checked ? serverField.unencryptedRegEx : serverField.encryptedRegEx } errorHighlight: !acceptableInput// && focus === true EnterKey.enabled: acceptableInput EnterKey.iconSource: "image://theme/icon-m-enter-next" diff --git a/qml/pages/NotesPage.qml b/qml/pages/NotesPage.qml index bd10784..0f6b71e 100644 --- a/qml/pages/NotesPage.qml +++ b/qml/pages/NotesPage.qml @@ -83,7 +83,7 @@ Page { currentIndex: -1 - model: noteListModel + model: api.model() delegate: BackgroundItem { id: note @@ -232,7 +232,7 @@ Page { horizontalAlignment: Qt.AlignHCenter text: qsTr("Loading notes...") } - + /* ViewPlaceholder { id: noLoginPlaceholder enabled: appSettings.accountIDs.length <= 0 @@ -260,7 +260,7 @@ Page { text: qsTr("An error occurred") hintText: api.statusText } - + */ TouchInteractionHint { id: addAccountHint interactionMode: TouchInteraction.Pull diff --git a/rpm/harbour-nextcloudnotes.spec b/rpm/harbour-nextcloudnotes.spec index 6795097..f691e1f 100644 --- a/rpm/harbour-nextcloudnotes.spec +++ b/rpm/harbour-nextcloudnotes.spec @@ -13,7 +13,7 @@ Name: harbour-nextcloudnotes %{!?qtc_make:%define qtc_make make} %{?qtc_builddir:%define _builddir %qtc_builddir} Summary: Nextcloud Notes -Version: 0.3 +Version: 0.4 Release: 0 Group: Applications/Editors License: MIT diff --git a/src/harbour-nextcloudnotes.cpp b/src/harbour-nextcloudnotes.cpp index 34bbfa7..f089328 100644 --- a/src/harbour-nextcloudnotes.cpp +++ b/src/harbour-nextcloudnotes.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "notesapi.h" #include "note.h" #include "notesmodel.h" #include "sslconfiguration.h" @@ -16,6 +17,7 @@ int main(int argc, char *argv[]) app->setOrganizationName("harbour-nextcloudnotes"); qDebug() << app->applicationDisplayName() << app->applicationVersion(); + qmlRegisterType("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi"); qmlRegisterType("harbour.nextcloudnotes.note", 1, 0, "Note"); qmlRegisterType("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel"); qmlRegisterType("harbour.nextcloudnotes.sslconfiguration", 1, 0, "SslConfiguration"); diff --git a/src/note.cpp b/src/note.cpp index 680e144..91b8104 100644 --- a/src/note.cpp +++ b/src/note.cpp @@ -41,14 +41,80 @@ Note& Note::operator=(const Note& note) { return *this; } -bool Note::equal(const Note& n) const { - return m_id == n.id() && - m_modified == n.modified() && - m_title == n.title() && - m_category == n.category() && - m_content == n.content() && - m_favorite == n.favorite() && - m_etag == n.etag() && - m_error == n.error() && - m_errorMessage == n.errorMessage(); +bool Note::operator==(const Note& note) const { + return same(note); +} + +bool Note::same(const Note& note) const { + return m_id == note.id(); +} + +bool Note::same(const int id) const { + return m_id == id; +} + +bool Note::equal(const Note& note) const { + return m_id == note.id() && + m_modified == note.modified() && + m_title == note.title() && + m_category == note.category() && + m_content == note.content() && + m_favorite == note.favorite() && + m_etag == note.etag() && + m_error == note.error() && + m_errorMessage == note.errorMessage(); +} + +bool Note::newer(const Note& note) const { + return same(note) && note.modified() > m_modified; +} + +bool Note::older(const Note& note) const { + return same(note) && note.modified() < m_modified; +} + +QString Note::dateString() const { + QDateTime date; + QString dateString; + date.setTime_t(m_modified); + qint64 diff = date.daysTo(QDateTime::currentDateTime()); + if (diff == 0) + dateString = tr("Today"); + else if (diff == 1) + dateString = tr("Yesterday"); + else if (diff < 7) + dateString = date.toLocalTime().toString("dddd"); + else if (date.toLocalTime().toString("yyyy") == QDateTime::currentDateTime().toString("yyyy")) + dateString = date.toLocalTime().toString("MMMM"); + else + dateString = date.toLocalTime().toString("MMMM yyyy"); + return dateString; +} + +Note Note::fromjson(const QJsonObject& jobj) { + Note note = new Note; + note.setId(jobj.value("id").toInt()); + note.setModified(jobj.value("modified").toInt()); + note.setTitle(jobj.value("title").toString()); + note.setCategory(jobj.value("category").toString()); + note.setContent(jobj.value("content").toString()); + note.setFavorite(jobj.value("favorite").toBool()); + note.setEtag(jobj.value("etag").toString()); + note.setError(jobj.value("error").toBool(true)); + note.setErrorMessage(jobj.value("errorMessage").toString()); + return note; +} + +bool Note::searchInNote(const QString &query, const Note ¬e, SearchAttributes criteria, Qt::CaseSensitivity cs) { + bool queryFound = false; + if (criteria.testFlag(SearchInTitle)) { + queryFound |= note.title().contains(query, cs); + } + if (criteria.testFlag(SearchInContent)) { + queryFound |= note.content().contains(query, cs); + } + if (criteria.testFlag(SearchInCategory)) { + queryFound |= note.category().contains(query, cs); + } + return queryFound; } diff --git a/src/note.h b/src/note.h index 02be5c6..a781677 100644 --- a/src/note.h +++ b/src/note.h @@ -24,6 +24,23 @@ public: Note(QObject *parent = NULL); Note(const Note& note, QObject *parent = NULL); + Note& operator=(const Note& note); + bool operator==(const Note& note) const; + bool same(const Note& note) const; + bool same(const int id) const; + bool equal(const Note& note) const; + bool newer(const Note& note) const; + bool older(const Note& note) const; + + enum SearchAttribute { + NoSearchAttribute = 0x0, + SearchInTitle = 0x1, + SearchInCategory = 0x2, + SearchInContent = 0x4, + SearchAll = 0x7 + }; + Q_DECLARE_FLAGS(SearchAttributes, SearchAttribute) + int id() const { return m_id; } uint modified() const { return m_modified; } QString title() const { return m_title; } @@ -33,23 +50,7 @@ public: QString etag() const { return m_etag; } bool error() const { return m_error; } QString errorMessage() const { return m_errorMessage; } - QString dateString() const { - QDateTime date; - QString dateString; - date.setTime_t(m_modified); - qint64 diff = date.daysTo(QDateTime::currentDateTime()); - if (diff == 0) - dateString = tr("Today"); - else if (diff == 1) - dateString = tr("Yesterday"); - else if (diff < 7) - dateString = date.toLocalTime().toString("dddd"); - else if (date.toLocalTime().toString("yyyy") == QDateTime::currentDateTime().toString("yyyy")) - dateString = date.toLocalTime().toString("MMMM"); - else - dateString = date.toLocalTime().toString("MMMM yyyy"); - return dateString; - } + QString dateString() const; void setId(int id) { if (id != m_id) { m_id = id; emit idChanged(id); } } void setModified(uint modified) { if (modified != m_modified) { m_modified = modified; emit modifiedChanged(modified); emit dateStringChanged(dateString()); } } @@ -61,45 +62,8 @@ public: void setError(bool error) { if (error != m_error) { m_error = error; emit errorChanged(error); } } void setErrorMessage(QString errorMessage) { if (errorMessage != m_errorMessage) { m_errorMessage = errorMessage; emit errorMessageChanged(errorMessage); } } - Note& operator=(const Note& note); - bool operator==(const Note& note) const { - return m_id == note.id(); - } - bool equal(const Note& n) const; - enum SearchAttribute { - NoSearchAttribute = 0x0, - SearchInTitle = 0x1, - SearchInCategory = 0x2, - SearchInContent = 0x4, - SearchAll = 0x7 - }; - Q_DECLARE_FLAGS(SearchAttributes, SearchAttribute) - static Note fromjson(const QJsonObject& jobj) { - Note note = new Note; - note.setId(jobj.value("id").toInt()); - note.setModified(jobj.value("modified").toInt()); - note.setTitle(jobj.value("title").toString()); - note.setCategory(jobj.value("category").toString()); - note.setContent(jobj.value("content").toString()); - note.setFavorite(jobj.value("favorite").toBool()); - note.setEtag(jobj.value("etag").toString()); - note.setError(jobj.value("error").toBool(true)); - note.setErrorMessage(jobj.value("errorMessage").toString()); - return note; - } - static bool searchInNote(const QString &query, const Note ¬e, SearchAttributes criteria = QFlag(SearchAll), Qt::CaseSensitivity cs = Qt::CaseInsensitive) { - bool queryFound = false; - if (criteria.testFlag(SearchInTitle)) { - queryFound |= note.title().contains(query, cs); - } - if (criteria.testFlag(SearchInContent)) { - queryFound |= note.content().contains(query, cs); - } - if (criteria.testFlag(SearchInCategory)) { - queryFound |= note.category().contains(query, cs); - } - return queryFound; - } + static Note fromjson(const QJsonObject& jobj); + static bool searchInNote(const QString &query, const Note ¬e, SearchAttributes criteria = QFlag(SearchAll), Qt::CaseSensitivity cs = Qt::CaseInsensitive); signals: void idChanged(int id); diff --git a/src/notesapi.cpp b/src/notesapi.cpp new file mode 100644 index 0000000..6c0ab8d --- /dev/null +++ b/src/notesapi.cpp @@ -0,0 +1,212 @@ +#include "notesapi.h" +#include +#include +#include +#include + +NotesApi::NotesApi(QObject *parent) : QObject(parent) +{ + mp_model = new NotesModel(this); + connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(verifyUrl(QUrl))); + 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*))); + connect(&m_manager, SIGNAL(sslErrors(QNetworkReply*,QList)), this, SLOT(sslError(QNetworkReply*,QList))); + m_request.setSslConfiguration(QSslConfiguration::defaultConfiguration()); + m_request.setHeader(QNetworkRequest::UserAgentHeader, QGuiApplication::applicationDisplayName() + " / " + QGuiApplication::applicationVersion()); + m_request.setRawHeader("OCS-APIREQUEST", "true"); + m_request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json").toUtf8()); +} + +NotesApi::~NotesApi() { + delete mp_model; +} + +void NotesApi::setSslVerify(bool verify) { + if (verify != (m_request.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer)) { + m_request.sslConfiguration().setPeerVerifyMode(verify ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone); + emit sslVerifyChanged(verify); + } +} + +void NotesApi::requireAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) { + if (reply && authenticator) { + authenticator->setUser(username()); + authenticator->setPassword(password()); + } +} + +void NotesApi::setUrl(QUrl url) { + if (url != m_url) { + QUrl oldUrl = m_url; + m_url = url; + emit urlChanged(m_url); + if (m_url.scheme() != oldUrl.scheme()) + emit schemeChanged(m_url.scheme()); + if (m_url.host() != oldUrl.host()) + emit hostChanged(m_url.host()); + if (m_url.port() != oldUrl.port()) + emit portChanged(m_url.port()); + if (m_url.userName() != oldUrl.userName()) + emit usernameChanged(m_url.userName()); + if (m_url.password() != oldUrl.password()) + emit passwordChanged(m_url.password()); + if (m_url.path() != oldUrl.path()) + emit pathChanged(m_url.path()); + qDebug() << "API URL changed:" << m_url.toDisplayString(); + } +} + +void NotesApi::setScheme(QString scheme) { + if (scheme == "http" || scheme == "https") { + QUrl url = m_url; + url.setScheme(scheme); + setUrl(url); + } +} + +void NotesApi::setHost(QString host) { + if (!host.isEmpty()) { + QUrl url = m_url; + url.setHost(host); + setUrl(url); + } +} + +void NotesApi::setPort(int port) { + if (port >= -1 && port <= 65535) { + QUrl url = m_url; + url.setPort(port); + setUrl(url); + } +} + +void NotesApi::setUsername(QString username) { + if (!username.isEmpty()) { + QUrl url = m_url; + url.setUserName(username); + QString concatenated = username + ":" + password(); + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; + m_request.setRawHeader("Authorization", headerData.toLocal8Bit()); + setUrl(url); + } +} + +void NotesApi::setPassword(QString password) { + if (!password.isEmpty()) { + QUrl url = m_url; + url.setPassword(password); + QString concatenated = username() + ":" + password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; + m_request.setRawHeader("Authorization", headerData.toLocal8Bit()); + setUrl(url); + } +} + +void NotesApi::setPath(QString path) { + if (!path.isEmpty()) { + QUrl url = m_url; + url.setPath(path); + setUrl(url); + } +} + +bool NotesApi::busy() const { + bool busy = false; + for (int i = 0; i < m_replies.size(); ++i) { + busy |=m_replies[i]->isRunning(); + } + return busy; +} + +void NotesApi::getAllNotes(QStringList excludeFields) { + QUrl url = m_url; + url.setPath(url.path() + "/notes"); + if (!excludeFields.isEmpty()) + url.setQuery(QString("exclude=").append(excludeFields.join(","))); + if (url.isValid()) { + qDebug() << "GET" << url.toDisplayString(); + m_request.setUrl(url); + m_replies << m_manager.get(m_request); + emit busyChanged(busy()); + } +} + +void NotesApi::getNote(int noteId, QStringList excludeFields) { + QUrl url = m_url; + url.setPath(url.path() + QString("/notes/%1").arg(noteId)); + if (!excludeFields.isEmpty()) + url.setQuery(QString("exclude=").append(excludeFields.join(","))); + if (url.isValid()) { + qDebug() << "GET" << url.toDisplayString(); + m_request.setUrl(url); + m_replies << m_manager.get(m_request); + emit busyChanged(busy()); + } +} + +void NotesApi::createNote(QVariantHash fields) { + QUrl url = m_url; + url.setPath(url.path() + "/notes"); + if (url.isValid()) { + qDebug() << "POST" << url.toDisplayString(); + m_request.setUrl(url); + m_replies << m_manager.post(m_request, QJsonDocument(QJsonObject::fromVariantHash(fields)).toJson()); + emit busyChanged(busy()); + } +} + +void NotesApi::updateNote(int noteId, QVariantHash fields) { + QUrl url = m_url; + url.setPath(url.path() + QString("/notes/%1").arg(noteId)); + if (url.isValid()) { + qDebug() << "PUT" << url.toDisplayString(); + m_request.setUrl(url); + m_replies << m_manager.put(m_request, QJsonDocument(QJsonObject::fromVariantHash(fields)).toJson()); + emit busyChanged(busy()); + } +} + +void NotesApi::deleteNote(int noteId) { + QUrl url = m_url; + url.setPath(url.path() + QString("/notes/%1").arg(noteId)); + if (url.isValid()) { + qDebug() << "DELETE" << url.toDisplayString(); + m_request.setUrl(url); + m_replies << m_manager.deleteResource(m_request); + emit busyChanged(busy()); + } +} + +void NotesApi::verifyUrl(QUrl url) { + emit urlValidChanged(url.isValid()); +} + +void NotesApi::onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible) { + qDebug() << m_manager.networkAccessible(); + emit networkAccessibleChanged(accessible == QNetworkAccessManager::Accessible); +} + +void NotesApi::replyFinished(QNetworkReply *reply) { + if (reply->error() == QNetworkReply::NoError) { + QJsonDocument json = QJsonDocument::fromJson(reply->readAll()); + if (mp_model) + mp_model->applyJSON(json); + //qDebug() << json; + } + else { + qDebug() << reply->errorString(); + } + m_replies.removeAll(reply); + reply->deleteLater(); + emit busyChanged(busy()); +} + +void NotesApi::sslError(QNetworkReply *reply, const QList &errors) { + qDebug() << "SSL errors accured while calling" << reply->url().toDisplayString(); + for (int i = 0; i < errors.size(); ++i) { + qDebug() << errors[i].errorString(); + } +} diff --git a/src/notesapi.h b/src/notesapi.h new file mode 100644 index 0000000..de8216b --- /dev/null +++ b/src/notesapi.h @@ -0,0 +1,96 @@ +#ifndef NOTESAPI_H +#define NOTESAPI_H + +#include +#include +#include +#include +#include +#include "notesmodel.h" + +class NotesApi : public QObject +{ + Q_OBJECT +public: + explicit NotesApi(QObject *parent = nullptr); + virtual ~NotesApi(); + + Q_PROPERTY(bool sslVerify READ sslVerify WRITE setSslVerify NOTIFY sslVerifyChanged) + bool sslVerify() const { return m_request.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 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(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged) + bool networkAccessible() const { return m_manager.networkAccessible() == QNetworkAccessManager::Accessible; } + + Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) + bool busy() const; + + Q_INVOKABLE void getAllNotes(QStringList excludeFields = QStringList()); + Q_INVOKABLE void getNote(int noteId, QStringList excludeFields = QStringList()); + Q_INVOKABLE void createNote(QVariantHash fields = QVariantHash()); + Q_INVOKABLE void updateNote(int noteId, QVariantHash fields = QVariantHash()); + Q_INVOKABLE void deleteNote(int noteId); + Q_INVOKABLE NotesModel& model() const { return *mp_model; } + +signals: + void sslVerifyChanged(bool verify); + void urlChanged(QUrl url); + void urlValidChanged(bool valid); + void schemeChanged(QString scheme); + void hostChanged(QString host); + void portChanged(int port); + void usernameChanged(QString username); + void passwordChanged(QString password); + void pathChanged(QString path); + void networkAccessibleChanged(bool accessible); + void busyChanged(bool busy); + +public slots: + +private slots: + void verifyUrl(QUrl url); + void requireAuthentication(QNetworkReply * reply, QAuthenticator * authenticator); + void onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible); + void replyFinished(QNetworkReply* reply); + void sslError(QNetworkReply* reply, const QList &errors); + +private: + QUrl m_url; + QNetworkAccessManager m_manager; + QNetworkRequest m_request; + QVector m_replies; + NotesModel* mp_model; +}; + +#endif // NOTESAPI_H diff --git a/src/notesmodel.cpp b/src/notesmodel.cpp index 9c8628b..29d0a09 100644 --- a/src/notesmodel.cpp +++ b/src/notesmodel.cpp @@ -86,11 +86,9 @@ bool NotesModel::applyJSONobject(const QJsonObject &jobj) { return true; } -bool NotesModel::applyJSON(const QString &json) { +bool NotesModel::applyJSON(const QJsonDocument &jdoc) { qDebug() << "Applying new JSON input";// << json; - QJsonParseError error; - QJsonDocument jdoc = QJsonDocument::fromJson(json.toUtf8(), &error); - if (!jdoc.isNull() && error.error == QJsonParseError::NoError) { + if (!jdoc.isNull()) { if (jdoc.isArray()) { qDebug() << "- It's an array..."; QJsonArray jarr = jdoc.array(); @@ -110,12 +108,20 @@ bool NotesModel::applyJSON(const QString &json) { } else { qDebug() << "Unknown JSON document. This message should never occure!"; - return false; } } else { - qDebug() << "Unable to parse the JSON input:" << error.errorString(); + qDebug() << "JSON document is empty!"; + } + return false; +} + +bool NotesModel::applyJSON(const QString &json) { + QJsonParseError error; + QJsonDocument jdoc = QJsonDocument::fromJson(json.toUtf8(), &error); + if (!jdoc.isNull() && error.error == QJsonParseError::NoError) { + return applyJSON(jdoc); } return error.error == QJsonParseError::NoError; } diff --git a/src/notesmodel.h b/src/notesmodel.h index e18ff43..af4f28c 100644 --- a/src/notesmodel.h +++ b/src/notesmodel.h @@ -32,6 +32,7 @@ public: Q_INVOKABLE void search(QString searchText = QString()); Q_INVOKABLE void clearSearch(); + Q_INVOKABLE bool applyJSON(const QJsonDocument &jdoc); Q_INVOKABLE bool applyJSON(const QString &json); Q_INVOKABLE int insertNote(const Note ¬e); Q_INVOKABLE bool removeNote(const Note ¬e); diff --git a/src/sslconfiguration.cpp b/src/sslconfiguration.cpp index b6792c9..006b385 100644 --- a/src/sslconfiguration.cpp +++ b/src/sslconfiguration.cpp @@ -16,7 +16,7 @@ void SslConfiguration::setCheckCert(bool check) { if (_checkCert != check) { qDebug() << "Changing SSL Cert check to" << check; _checkCert = check; - emit checkCertChanged(_checkCert); QSslConfiguration::setDefaultConfiguration(_checkCert ? checkCertConfig : noCheckConfig); + emit checkCertChanged(_checkCert); } } diff --git a/translations/harbour-nextcloudnotes-de.ts b/translations/harbour-nextcloudnotes-de.ts index 9d87965..3f65e3f 100644 --- a/translations/harbour-nextcloudnotes-de.ts +++ b/translations/harbour-nextcloudnotes-de.ts @@ -119,10 +119,6 @@ Nextcloud server Nextcloud Server URL - - (starting with "https://") - (beginnend mit "https://") - Username Benutzername @@ -174,21 +170,6 @@ Gestern - - NoteDelegateModel - - Modified - Geändert - - - Delete - Löschen - - - Deleting note - Lösche Notiz - - NotePage @@ -243,80 +224,52 @@ NotesPage Settings - Einstellungen + Einstellungen Add note - Notiz hinzufügen + Reload - Neu laden + Neu laden Updating... - Aktualisiere... + Aktualisiere... Last update - Zuletzt aktualisiert + Zuletzt aktualisiert never - noch nie - - - No account yet - Noch kein Konto eingerichtet - - - Got to the settings to add an account - Gehe in die Einstellungen um ein Konto hinzuzufügen - - - No notes yet - Keine Notizen vorhanden - - - Pull down to add a note - Ziehe nach unten um eine Notiz zu erstellen - - - No result - Nichts gefunden - - - Try another query - Probiere eine andere Suche - - - An error occurred - Ein Fehler ist aufgetreten - - - Open the settings to configure your Nextcloud accounts - Gehe in die Einstellungen um deine Nextcloud Konten zu verwalten + noch nie Nextcloud Notes - Nextcloud Notizen + Nextcloud Notizen Modified - Geändert + Geändert Delete - Löschen + Löschen Deleting note - Lösche Notiz + Loading notes... + + Open the settings to configure your Nextcloud accounts + + SettingsPage diff --git a/translations/harbour-nextcloudnotes-sv.ts b/translations/harbour-nextcloudnotes-sv.ts index 58d885b..b62afb1 100644 --- a/translations/harbour-nextcloudnotes-sv.ts +++ b/translations/harbour-nextcloudnotes-sv.ts @@ -107,10 +107,6 @@ Login Logga in - - (starting with "https://") - (börjar med "https://") - Username Användarnamn @@ -174,21 +170,6 @@ - - NoteDelegateModel - - Modified - Ändrad - - - Delete - Ta bort - - - Deleting note - Tar bort anteckning - - NotePage @@ -243,59 +224,27 @@ NotesPage Settings - Inställningar + Inställningar Add note - Lägg till anteckning + Reload - Uppdatera + Uppdatera Updating... - Uppdaterar... + Uppdaterar... Last update - Senaste uppdatering + Senaste uppdatering never - aldrig - - - No account yet - Inget konto ännu - - - Got to the settings to add an account - Gå till inställningarna för att lägga till ett konto - - - No notes yet - Inga anteckningar ännu - - - Pull down to add a note - Dra neråt för att lägga till anteckning - - - No result - Inget resultat - - - Try another query - Försök med en annan söksträng - - - An error occurred - Ett fel inträffade - - - Open the settings to configure your Nextcloud accounts - Öppna inställningarna för att konfigurera dina Nextcloud-konton + aldrig Nextcloud Notes @@ -311,12 +260,16 @@ Deleting note - Tar bort anteckning + Loading notes... + + Open the settings to configure your Nextcloud accounts + + SettingsPage diff --git a/translations/harbour-nextcloudnotes.ts b/translations/harbour-nextcloudnotes.ts index d5baf70..4bc858d 100644 --- a/translations/harbour-nextcloudnotes.ts +++ b/translations/harbour-nextcloudnotes.ts @@ -144,11 +144,6 @@ Nextcloud server - - - (starting with "https://") - - Username @@ -201,34 +196,16 @@ Note - + Today - + Yesterday - - NoteDelegateModel - - - Modified - - - - - Delete - - - - - Deleting note - - - NotePage @@ -347,41 +324,6 @@ Loading notes... - - - No account yet - - - - - Got to the settings to add an account - - - - - No notes yet - - - - - Pull down to add a note - - - - - No result - - - - - Try another query - - - - - An error occurred - - Open the settings to configure your Nextcloud accounts