Implemented the API in C++. Still plenty bugs.

This commit is contained in:
Scharel Clemens 2019-11-10 03:35:33 +01:00
parent 85b33a9e37
commit a026c2ee8d
17 changed files with 488 additions and 515 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@
#include <sailfishapp.h>
#include <QtQml>
#include <QObject>
#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<NotesApi>("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi");
qmlRegisterType<Note>("harbour.nextcloudnotes.note", 1, 0, "Note");
qmlRegisterType<NotesModel>("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel");
qmlRegisterType<SslConfiguration>("harbour.nextcloudnotes.sslconfiguration", 1, 0, "SslConfiguration");

View file

@ -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 &note, 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;
}

View file

@ -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 &note, 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 &note, SearchAttributes criteria = QFlag(SearchAll), Qt::CaseSensitivity cs = Qt::CaseInsensitive);
signals:
void idChanged(int id);

212
src/notesapi.cpp Normal file
View file

@ -0,0 +1,212 @@
#include "notesapi.h"
#include <QGuiApplication>
#include <QAuthenticator>
#include <QJsonDocument>
#include <QJsonObject>
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<QSslError>)), this, SLOT(sslError(QNetworkReply*,QList<QSslError>)));
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<QSslError> &errors) {
qDebug() << "SSL errors accured while calling" << reply->url().toDisplayString();
for (int i = 0; i < errors.size(); ++i) {
qDebug() << errors[i].errorString();
}
}

96
src/notesapi.h Normal file
View file

@ -0,0 +1,96 @@
#ifndef NOTESAPI_H
#define NOTESAPI_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDebug>
#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<QSslError> &errors);
private:
QUrl m_url;
QNetworkAccessManager m_manager;
QNetworkRequest m_request;
QVector<QNetworkReply*> m_replies;
NotesModel* mp_model;
};
#endif // NOTESAPI_H

View file

@ -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;
}

View file

@ -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 &note);
Q_INVOKABLE bool removeNote(const Note &note);

View file

@ -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);
}
}

View file

@ -119,10 +119,6 @@
<source>Nextcloud server</source>
<translation>Nextcloud Server URL</translation>
</message>
<message>
<source>(starting with &quot;https://&quot;)</source>
<translation>(beginnend mit &quot;https://&quot;)</translation>
</message>
<message>
<source>Username</source>
<translation>Benutzername</translation>
@ -174,21 +170,6 @@
<translation>Gestern</translation>
</message>
</context>
<context>
<name>NoteDelegateModel</name>
<message>
<source>Modified</source>
<translation>Geändert</translation>
</message>
<message>
<source>Delete</source>
<translation>Löschen</translation>
</message>
<message>
<source>Deleting note</source>
<translation>Lösche Notiz</translation>
</message>
</context>
<context>
<name>NotePage</name>
<message>
@ -243,80 +224,52 @@
<name>NotesPage</name>
<message>
<source>Settings</source>
<translation>Einstellungen</translation>
<translation type="unfinished">Einstellungen</translation>
</message>
<message>
<source>Add note</source>
<translation>Notiz hinzufügen</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reload</source>
<translation>Neu laden</translation>
<translation type="unfinished">Neu laden</translation>
</message>
<message>
<source>Updating...</source>
<translation>Aktualisiere...</translation>
<translation type="unfinished">Aktualisiere...</translation>
</message>
<message>
<source>Last update</source>
<translation>Zuletzt aktualisiert</translation>
<translation type="unfinished">Zuletzt aktualisiert</translation>
</message>
<message>
<source>never</source>
<translation>noch nie</translation>
</message>
<message>
<source>No account yet</source>
<translation>Noch kein Konto eingerichtet</translation>
</message>
<message>
<source>Got to the settings to add an account</source>
<translation>Gehe in die Einstellungen um ein Konto hinzuzufügen</translation>
</message>
<message>
<source>No notes yet</source>
<translation>Keine Notizen vorhanden</translation>
</message>
<message>
<source>Pull down to add a note</source>
<translation>Ziehe nach unten um eine Notiz zu erstellen</translation>
</message>
<message>
<source>No result</source>
<translation>Nichts gefunden</translation>
</message>
<message>
<source>Try another query</source>
<translation>Probiere eine andere Suche</translation>
</message>
<message>
<source>An error occurred</source>
<translation>Ein Fehler ist aufgetreten</translation>
</message>
<message>
<source>Open the settings to configure your Nextcloud accounts</source>
<translation>Gehe in die Einstellungen um deine Nextcloud Konten zu verwalten</translation>
<translation type="unfinished">noch nie</translation>
</message>
<message>
<source>Nextcloud Notes</source>
<translation>Nextcloud Notizen</translation>
<translation type="unfinished">Nextcloud Notizen</translation>
</message>
<message>
<source>Modified</source>
<translation>Geändert</translation>
<translation type="unfinished">Geändert</translation>
</message>
<message>
<source>Delete</source>
<translation>Löschen</translation>
<translation type="unfinished">Löschen</translation>
</message>
<message>
<source>Deleting note</source>
<translation>Lösche Notiz</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Loading notes...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open the settings to configure your Nextcloud accounts</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>

View file

@ -107,10 +107,6 @@
<source>Login</source>
<translation>Logga in</translation>
</message>
<message>
<source>(starting with &quot;https://&quot;)</source>
<translation>(börjar med &quot;https://&quot;)</translation>
</message>
<message>
<source>Username</source>
<translation>Användarnamn</translation>
@ -174,21 +170,6 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NoteDelegateModel</name>
<message>
<source>Modified</source>
<translation type="unfinished">Ändrad</translation>
</message>
<message>
<source>Delete</source>
<translation type="unfinished">Ta bort</translation>
</message>
<message>
<source>Deleting note</source>
<translation type="unfinished">Tar bort anteckning</translation>
</message>
</context>
<context>
<name>NotePage</name>
<message>
@ -243,59 +224,27 @@
<name>NotesPage</name>
<message>
<source>Settings</source>
<translation>Inställningar</translation>
<translation type="unfinished">Inställningar</translation>
</message>
<message>
<source>Add note</source>
<translation>Lägg till anteckning</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Reload</source>
<translation>Uppdatera</translation>
<translation type="unfinished">Uppdatera</translation>
</message>
<message>
<source>Updating...</source>
<translation>Uppdaterar...</translation>
<translation type="unfinished">Uppdaterar...</translation>
</message>
<message>
<source>Last update</source>
<translation>Senaste uppdatering</translation>
<translation type="unfinished">Senaste uppdatering</translation>
</message>
<message>
<source>never</source>
<translation>aldrig</translation>
</message>
<message>
<source>No account yet</source>
<translation>Inget konto ännu</translation>
</message>
<message>
<source>Got to the settings to add an account</source>
<translation> till inställningarna för att lägga till ett konto</translation>
</message>
<message>
<source>No notes yet</source>
<translation>Inga anteckningar ännu</translation>
</message>
<message>
<source>Pull down to add a note</source>
<translation>Dra neråt för att lägga till anteckning</translation>
</message>
<message>
<source>No result</source>
<translation>Inget resultat</translation>
</message>
<message>
<source>Try another query</source>
<translation>Försök med en annan söksträng</translation>
</message>
<message>
<source>An error occurred</source>
<translation>Ett fel inträffade</translation>
</message>
<message>
<source>Open the settings to configure your Nextcloud accounts</source>
<translation>Öppna inställningarna för att konfigurera dina Nextcloud-konton</translation>
<translation type="unfinished">aldrig</translation>
</message>
<message>
<source>Nextcloud Notes</source>
@ -311,12 +260,16 @@
</message>
<message>
<source>Deleting note</source>
<translation type="unfinished">Tar bort anteckning</translation>
<translation type="unfinished"></translation>
</message>
<message>
<source>Loading notes...</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open the settings to configure your Nextcloud accounts</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>

View file

@ -144,11 +144,6 @@
<source>Nextcloud server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginDialog.qml" line="80"/>
<source>(starting with &quot;https://&quot;)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/LoginDialog.qml" line="93"/>
<source>Username</source>
@ -201,34 +196,16 @@
<context>
<name>Note</name>
<message>
<location filename="../src/note.h" line="42"/>
<location filename="../src/note.cpp" line="82"/>
<source>Today</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/note.h" line="44"/>
<location filename="../src/note.cpp" line="84"/>
<source>Yesterday</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NoteDelegateModel</name>
<message>
<location filename="../qml/components/NoteDelegateModel.qml" line="231"/>
<source>Modified</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/components/NoteDelegateModel.qml" line="234"/>
<source>Delete</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/components/NoteDelegateModel.qml" line="236"/>
<source>Deleting note</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NotePage</name>
<message>
@ -347,41 +324,6 @@
<source>Loading notes...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="239"/>
<source>No account yet</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="240"/>
<source>Got to the settings to add an account</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="246"/>
<source>No notes yet</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="247"/>
<source>Pull down to add a note</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="253"/>
<source>No result</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="254"/>
<source>Try another query</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="260"/>
<source>An error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/pages/NotesPage.qml" line="271"/>
<source>Open the settings to configure your Nextcloud accounts</source>