Implementing new methods for data handling (files and API targets)

This commit is contained in:
Scharel Clemens 2020-04-05 22:59:18 +02:00
parent ffffd306d6
commit 60e1eb0bdd
16 changed files with 334 additions and 65 deletions

View file

@ -18,12 +18,15 @@ DEFINES += APP_VERSION=\\\"$$VERSION\\\"
HEADERS += src/note.h \
src/notesapi.h \
src/notesmodel.h
src/notesinterface.h \
src/notesmodel.h \
src/notesstore.h
SOURCES += src/harbour-nextcloudnotes.cpp \
src/note.cpp \
src/notesapi.cpp \
src/notesmodel.cpp
src/notesmodel.cpp \
src/notesstore.cpp
DISTFILES += qml/harbour-nextcloudnotes.qml \
qml/cover/CoverPage.qml \

View file

@ -2,7 +2,10 @@ import QtQuick 2.2
import Sailfish.Silica 1.0
import Nemo.Configuration 1.0
import Nemo.Notifications 1.0
import harbour.nextcloudnotes.note 1.0
import harbour.nextcloudnotes.notesstore 1.0
import harbour.nextcloudnotes.notesapi 1.0
import harbour.nextcloudnotes.notesmodel 1.0
import "pages"
ApplicationWindow
@ -33,7 +36,10 @@ ApplicationWindow
onUsernameChanged: notesApi.username = username
onPasswordChanged: notesApi.password = password
onDoNotVerifySslChanged: notesApi.sslVerify = !doNotVerifySsl
onPathChanged: notesApi.dataFile = appSettings.currentAccount !== "" ? StandardPaths.data + "/" + appSettings.currentAccount + ".json" : ""
onPathChanged: {
notesStore.account = appSettings.currentAccount
notesApi.dataFile = appSettings.currentAccount !== "" ? StandardPaths.data + "/" + appSettings.currentAccount + ".json" : ""
}
}
// General settings of the app
@ -121,6 +127,7 @@ ApplicationWindow
running: interval > 0 && notesApi.networkAccessible && appWindow.visible
triggeredOnStart: true
onTriggered: {
notesStore.getAllNotes()
if (!notesApi.busy) {
notesApi.getAllNotes();
}
@ -135,6 +142,26 @@ ApplicationWindow
}
}
NotesStore {
id: notesStore
Component.onCompleted: getAllNotes()
onAccountChanged: {
console.log(account)
if (account !== "")
getAllNotes()
}
onNoteCreated: {
//console.log("Note created", createdNote.id)
}
onNoteUpdated: {
//console.log("Note updated", updatedNote.id)
}
onNoteDeleted: {
//console.log("Note deleted", deletedNoteId)
}
}
NotesApi {
id: notesApi
@ -143,7 +170,8 @@ ApplicationWindow
networkAccessible ? offlineNotification.close(Notification.Closed) : offlineNotification.publish()
}
onError: {
console.log("Error (" + error + "): " + errorMessage(error))
if (error)
console.log("Error (" + error + "): " + errorMessage(error))
errorNotification.close()
if (error && networkAccessible) {
errorNotification.body = errorMessage(error)

View file

@ -12,6 +12,9 @@
# * date Author's Name <author's email> version-release
# - Summary of changes
* Sun Apr 05 2020 Scharel Clemens <harbour-nextcloudnotes@scharel.rocks> 0.6-1
- Implementing new methods for data handling (files and API targets)
* Sun Mar 29 2020 Scharel Clemens <harbour-nextcloudnotes@scharel.rocks> 0.6-0
- Improved changes of 0.5

View file

@ -14,7 +14,7 @@ Name: harbour-nextcloudnotes
%{?qtc_builddir:%define _builddir %qtc_builddir}
Summary: Nextcloud Notes
Version: 0.6
Release: 0
Release: 1
Group: Applications/Editors
License: MIT
URL: https://github.com/scharel/harbour-nextcloudnotes

View file

@ -1,7 +1,7 @@
Name: harbour-nextcloudnotes
Summary: Nextcloud Notes
Version: 0.6
Release: 0
Release: 1
# The contents of the Group field should be one of the groups listed here:
# https://github.com/mer-tools/spectacle/blob/master/data/GROUPS
Group: Applications/Editors

View file

@ -2,8 +2,9 @@
#include <sailfishapp.h>
#include <QtQml>
#include <QObject>
#include "notesapi.h"
#include "note.h"
#include "notesapi.h"
#include "notesstore.h"
#include "notesmodel.h"
int main(int argc, char *argv[])
@ -17,13 +18,13 @@ int main(int argc, char *argv[])
qDebug() << app->applicationDisplayName() << app->applicationVersion();
qRegisterMetaType<Note>();
qmlRegisterType<Note>("harbour.nextcloudnotes.note", 1, 0, "Note");
qmlRegisterType<NotesApi>("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi");
qmlRegisterType<NotesStore>("harbour.nextcloudnotes.notesstore", 1, 0, "NotesStore");
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 file

@ -4,6 +4,10 @@ Note::Note(QObject *parent) : QObject(parent) {
connectSignals();
}
Note::~Note() {
//qDebug() << "Note destroyed: " << id();
}
Note::Note(const Note& note, QObject *parent) : QObject(parent) {
setId(note.id());
setModified(note.modified());
@ -111,26 +115,26 @@ QJsonDocument Note::toJsonDocument() const {
return QJsonDocument(m_json);
}
double Note::id() const {
return m_json.value(ID).toDouble(-1);
int Note::id() const {
return m_json.value(ID).toInt(-1);
}
double Note::id(const QJsonObject &jobj) {
return jobj.value(ID).toDouble(-1);
int Note::id(const QJsonObject &jobj) {
return jobj.value(ID).toInt(-1);
}
void Note::setId(double id) {
void Note::setId(int id) {
if (id != this->id()) {
m_json.insert(ID, QJsonValue(id));
emit idChanged(this->id());
}
}
double Note::modified() const {
return m_json.value(MODIFIED).toDouble();
int Note::modified() const {
return m_json.value(MODIFIED).toInt();
}
double Note::modified(const QJsonObject &jobj) {
return jobj.value(MODIFIED).toDouble();
int Note::modified(const QJsonObject &jobj) {
return jobj.value(MODIFIED).toInt();
}
void Note::setModified(double modified) {
void Note::setModified(int modified) {
if (modified != this->modified()){
m_json.insert(MODIFIED, QJsonValue(modified));
emit modifiedChanged(this->modified());
@ -260,8 +264,8 @@ QDateTime Note::modifiedDateTime(const QJsonObject &jobj) {
}
void Note::connectSignals() {
connect(this, SIGNAL(idChanged(double)), this, SIGNAL(noteChanged()));
connect(this, SIGNAL(modifiedChanged(double)), this, SIGNAL(noteChanged()));
connect(this, SIGNAL(idChanged(int)), this, SIGNAL(noteChanged()));
connect(this, SIGNAL(modifiedChanged(int)), this, SIGNAL(noteChanged()));
connect(this, SIGNAL(titleChanged(QString)), this, SIGNAL(noteChanged()));
connect(this, SIGNAL(categoryChanged(QString)), this, SIGNAL(noteChanged()));
connect(this, SIGNAL(contentChanged(QString)), this, SIGNAL(noteChanged()));

View file

@ -24,6 +24,7 @@ public:
Note(QObject *parent = NULL);
Note(const Note& note, QObject *parent = NULL);
Note(const QJsonObject& note, QObject *parent = NULL);
~Note();
Note& operator =(const Note& note);
Note& operator =(const QJsonObject& note);
@ -40,49 +41,49 @@ public:
QJsonValue toJsonValue() const;
QJsonDocument toJsonDocument() const;
Q_PROPERTY(double id READ id WRITE setId NOTIFY idChanged)
double id() const;
void setId(double id);
Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)
Q_INVOKABLE int id() const;
void setId(int id);
Q_PROPERTY(double modified READ modified WRITE setModified NOTIFY modifiedChanged)
double modified() const;
void setModified(double modified);
Q_PROPERTY(int modified READ modified WRITE setModified NOTIFY modifiedChanged)
Q_INVOKABLE int modified() const;
void setModified(int modified);
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
QString title() const;
Q_INVOKABLE QString title() const;
void setTitle(QString title);
Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged)
QString category() const;
Q_INVOKABLE QString category() const;
void setCategory(QString category);
Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged)
QString content() const;
Q_INVOKABLE QString content() const;
void setContent(QString content);
Q_PROPERTY(bool favorite READ favorite WRITE setFavorite NOTIFY favoriteChanged)
bool favorite() const;
Q_INVOKABLE bool favorite() const;
void setFavorite(bool favorite);
Q_PROPERTY(QString etag READ etag WRITE setEtag NOTIFY etagChanged)
QString etag() const;
Q_INVOKABLE QString etag() const;
void setEtag(QString etag);
Q_PROPERTY(bool error READ error WRITE setError NOTIFY errorChanged)
bool error() const;
Q_INVOKABLE bool error() const;
void setError(bool error);
Q_PROPERTY(QString errorMessage READ errorMessage WRITE setErrorMessage NOTIFY errorMessageChanged)
QString errorMessage() const;
Q_INVOKABLE QString errorMessage() const;
void setErrorMessage(QString errorMessage);
Q_PROPERTY(QString modifiedString READ modifiedString NOTIFY modifiedStringChanged)
QString modifiedString() const;
Q_INVOKABLE QString modifiedString() const;
QDateTime modifiedDateTime() const;
Q_INVOKABLE QDateTime modifiedDateTime() const;
static double id(const QJsonObject& jobj);
static double modified(const QJsonObject& jobj);
static int id(const QJsonObject& jobj);
static int modified(const QJsonObject& jobj);
static QString title(const QJsonObject& jobj);
static QString category(const QJsonObject& jobj);
static QString content(const QJsonObject& jobj);
@ -94,8 +95,8 @@ public:
static QDateTime modifiedDateTime(const QJsonObject& jobj);
signals:
void idChanged(double id);
void modifiedChanged(double modified);
void idChanged(int id);
void modifiedChanged(int modified);
void titleChanged(QString title);
void categoryChanged(QString category);
void contentChanged(QString content);
@ -113,4 +114,6 @@ private:
void connectSignals();
};
Q_DECLARE_METATYPE(Note)
#endif // NOTE_H

View file

@ -261,7 +261,7 @@ void NotesApi::getAllNotes(QStringList excludeFields) {
}
}
void NotesApi::getNote(double noteId, QStringList excludeFields) {
void NotesApi::getNote(int noteId, QStringList excludeFields) {
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/notes/%1").arg(noteId));
if (!excludeFields.isEmpty())
url.setQuery(QString("exclude=").append(excludeFields.join(",")));
@ -288,7 +288,7 @@ void NotesApi::createNote(QVariantMap fields) {
}
}
void NotesApi::updateNote(double noteId, QVariantMap fields) {
void NotesApi::updateNote(int noteId, QVariantMap fields) {
// Update note in the model
Note note(QJsonObject::fromVariantMap(fields));
mp_model->insertNote(note);
@ -303,7 +303,7 @@ void NotesApi::updateNote(double noteId, QVariantMap fields) {
}
}
void NotesApi::deleteNote(double noteId) {
void NotesApi::deleteNote(int noteId) {
// Remove note from the model
mp_model->removeNote(noteId);
@ -350,9 +350,6 @@ const QString NotesApi::errorMessage(ErrorCodes error) const {
void NotesApi::verifyUrl(QUrl url) {
emit urlValidChanged(url.isValid());
if (m_url.isValid() && !m_url.scheme().isEmpty() && !m_url.host().isEmpty()) {
getNcStatus();
}
}
void NotesApi::requireAuthentication(QNetworkReply *reply, QAuthenticator *authenticator) {

View file

@ -53,7 +53,7 @@ public:
const QString loginEndpoint = LOGIN_ENDPOINT,
const QString ocsEndpoint = OCS_ENDPOINT,
const QString notesEndpoint = NOTES_ENDPOINT,
QObject *parent = NULL);
QObject *parent = nullptr);
virtual ~NotesApi();
bool sslVerify() const { return m_authenticatedRequest.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer; }
@ -131,10 +131,10 @@ public:
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 getNote(int noteId, QStringList excludeFields = QStringList());
Q_INVOKABLE void createNote(QVariantMap fields = QVariantMap());
Q_INVOKABLE void updateNote(double noteId, QVariantMap fields = QVariantMap());
Q_INVOKABLE void deleteNote(double noteId);
Q_INVOKABLE void updateNote(int noteId, QVariantMap fields = QVariantMap());
Q_INVOKABLE void deleteNote(int noteId);
Q_INVOKABLE NotesProxyModel* model() const { return mp_modelProxy; }
enum ErrorCodes {

43
src/notesinterface.h Normal file
View file

@ -0,0 +1,43 @@
#ifndef NOTESINTERFACE_H
#define NOTESINTERFACE_H
#include <QObject>
#include <QVector>
#include "note.h"
class NotesInterface : public QObject
{
Q_OBJECT
Q_PROPERTY(QString account READ account WRITE setAccount NOTIFY accountChanged)
Q_CLASSINFO("author", "Scharel Clemens")
Q_CLASSINFO("url", "https://github.com/scharel/harbour-nextcloudnotes")
public:
explicit NotesInterface(QObject *parent = nullptr) : QObject(parent) {
}
virtual QString account() const = 0;
virtual void setAccount(const QString& account) = 0;
Q_INVOKABLE virtual void getAllNotes() = 0;
Q_INVOKABLE virtual void getNote(const int id) = 0;
Q_INVOKABLE virtual void getNote(const Note& note) = 0;
Q_INVOKABLE virtual void createNote(const Note& note) = 0;
Q_INVOKABLE virtual void updateNote(const Note& note) = 0;
Q_INVOKABLE virtual void deleteNote(const int id) = 0;
Q_INVOKABLE virtual void deleteNote(const Note& note) = 0;
Q_INVOKABLE virtual Note* noteData(const int id) = 0;
Q_INVOKABLE virtual Note* noteData(const Note& note) = 0;
signals:
void accountChanged(const QString& account);
void noteCreated(Note* createdNote);
void noteUpdated(Note* updatedNote);
void noteDeleted(int deletedNoteId);
public slots:
};
#endif // NOTESINTERFACE_H

View file

@ -55,7 +55,7 @@ bool NotesModel::fromJsonDocument(const QJsonDocument &jdoc) {
if (!jdoc.isNull() && !jdoc.isEmpty()) {
if (jdoc.isArray()) {
//qDebug() << "- It's an array...";
QVector<double> notesIdsToRemove;
QVector<int> notesIdsToRemove;
QJsonArray jarr = jdoc.array();
if (!jarr.empty())
notesIdsToRemove = ids();
@ -104,8 +104,8 @@ QJsonDocument NotesModel::toJsonDocument() const {
return QJsonDocument(jarr);
}
QVector<double> NotesModel::ids() const {
QVector<double> ids;
QVector<int> NotesModel::ids() const {
QVector<int> ids;
for (int i = 0; i < m_notes.size(); ++i) {
ids.append(m_notes[i].id());
}
@ -149,7 +149,7 @@ bool NotesModel::removeNote(const Note &note) {
return false;
}
bool NotesModel::removeNote(double id) {
bool NotesModel::removeNote(int id) {
return removeNote(Note(QJsonObject{ {"id", id} } ));
}
@ -202,7 +202,7 @@ bool NotesModel::setData(const QModelIndex &index, const QVariant &value, int ro
if (index.isValid()) {
switch (role) {
case IdRole: {
double id = value.toDouble(&retval);
double id = value.toInt(&retval);
if (retval && id != m_notes[index.row()].id()) {
m_notes[index.row()].setId(id);
emit dataChanged(index, index, QVector<int>{ IdRole });
@ -210,7 +210,7 @@ bool NotesModel::setData(const QModelIndex &index, const QVariant &value, int ro
break;
}
case ModifiedRole: {
double modified = value.toDouble(&retval);
double modified = value.toInt(&retval);
if (retval && modified != m_notes[index.row()].modified()) {
m_notes[index.row()].setModified(modified);
emit dataChanged(index, index, QVector<int>{ ModifiedRole });

View file

@ -43,7 +43,7 @@ public:
int insertNote(const Note &note);
bool removeNote(const Note &note);
bool removeNote(double id);
bool removeNote(int id);
enum NoteRoles {
IdRole = Qt::UserRole,
@ -69,7 +69,7 @@ public:
protected:
//void addNote(const QJsonValue &note);
QVector<double> ids() const;
QVector<int> ids() const;
//int indexOf(const Note &note) const;
//int indexOf(int id) const;
//bool replaceNote(const Note &note);

147
src/notesstore.cpp Normal file
View file

@ -0,0 +1,147 @@
#include "notesstore.h"
#include <QDebug>
NotesStore::NotesStore(QString directory, QObject *parent) : NotesInterface(parent)
{
m_dir.setCurrent(directory);
m_dir.setPath("");
m_dir.setFilter(QDir::Files);
m_dir.setNameFilters( { "*.json" } );
m_note = nullptr;
}
NotesStore::~NotesStore() {
if (m_note)
m_note->deleteLater();
m_note = nullptr;
}
QString NotesStore::account() const {
QString dir;
if (m_dir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
dir = m_dir.dirName();
}
return dir;
}
void NotesStore::setAccount(const QString& account) {
//qDebug() << account << m_dir.path();
if (account != m_dir.path()) {
if (m_dir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
m_dir.cdUp();
}
if (!account.isEmpty()) {
m_dir.setPath(account);
if (!m_dir.mkpath(".")) {
qDebug() << "Failed to create or already present: " << m_dir.path();
}
}
//qDebug() << account << m_dir.path();
emit accountChanged(m_dir.path());
}
}
void NotesStore::getAllNotes() {
QFileInfoList files = m_dir.entryInfoList();
for (int i = 0; i < files.size(); ++i) {
bool ok;
int id = files[i].baseName().toInt(&ok);
if (ok) {
getNote(id);
}
}
}
void NotesStore::getNote(const int id) {
QFileInfo file(m_dir, QString("%1.json").arg(id));
if (file.exists()) {
emit noteUpdated(noteData(id));
}
}
void NotesStore::getNote(const Note& note) {
getNote(note.id());
}
void NotesStore::createNote(const Note& note) {
if (note.id() < 0) {
// TODO probably crate files with an '.json.<NUMBER>.new' extension
qDebug() << "Creating notes without the server API is not supported yet!";
}
else {
updateNote(note);
}
}
void NotesStore::updateNote(const Note& note) {
if (note.id() >= 0) {
QFileInfo fileinfo(m_dir, QString("%1.json").arg(note.id()));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QJsonDocument fileJson = QJsonDocument::fromBinaryData(file.readAll());
file.close();
if (!note.equal(fileJson.object())) {
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
QByteArray data = note.toJsonDocument().toJson();
if (file.write(data) == data.size()) {
emit noteUpdated(noteData(note));
}
}
}
file.close();
}
}
else {
if (file.open(QIODevice::ReadWrite | QIODevice::Text)) {
QByteArray data = note.toJsonDocument().toJson();
if (file.write(data) == data.size()) {
emit noteCreated(noteData(note));
}
file.close();
}
}
}
else {
createNote(note);
}
}
void NotesStore::deleteNote(const int id) {
QFileInfo fileinfo(m_dir, QString("%1.json").arg(id));
if (fileinfo.exists()) {
QFile file(fileinfo.filePath());
if (file.remove()) {
emit noteDeleted(id);
if (m_note)
m_note->deleteLater();
m_note = nullptr;
}
}
}
void NotesStore::deleteNote(const Note& note) {
deleteNote(note.id());
}
Note* NotesStore::noteData(const int id) {
if (m_note)
m_note->deleteLater();
m_note = nullptr;
QFileInfo fileinfo(m_dir, QString("%1.json").arg(id));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QByteArray data = file.readAll();
QJsonDocument json = QJsonDocument::fromJson(data);
m_note = new Note(json.object());
file.close();
}
}
return m_note;
}
Note* NotesStore::noteData(const Note& note) {
return noteData(note.id());
}

40
src/notesstore.h Normal file
View file

@ -0,0 +1,40 @@
#ifndef NOTESSTORE_H
#define NOTESSTORE_H
#include "notesinterface.h"
#include <QObject>
#include <QStandardPaths>
#include <QDir>
class NotesStore : public NotesInterface
{
Q_OBJECT
public:
explicit NotesStore(
QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation),
QObject *parent = nullptr);
virtual ~NotesStore();
QString account() const;
void setAccount(const QString& account);
signals:
public slots:
Q_INVOKABLE void getAllNotes();
Q_INVOKABLE void getNote(const int id);
Q_INVOKABLE void getNote(const Note& note);
Q_INVOKABLE void createNote(const Note& note);
Q_INVOKABLE void updateNote(const Note& note);
Q_INVOKABLE void deleteNote(const int id);
Q_INVOKABLE void deleteNote(const Note& note);
Q_INVOKABLE Note* noteData(const int id);
Q_INVOKABLE Note* noteData(const Note& note);
private:
QDir m_dir;
Note* m_note;
};
#endif // NOTESSTORE_H

View file

@ -245,12 +245,12 @@
<context>
<name>Note</name>
<message>
<location filename="../src/note.cpp" line="241"/>
<location filename="../src/note.cpp" line="245"/>
<source>Today</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/note.cpp" line="243"/>
<location filename="../src/note.cpp" line="247"/>
<source>Yesterday</source>
<translation type="unfinished"></translation>
</message>
@ -840,22 +840,22 @@ You can also use other markdown syntax inside them.</source>
<context>
<name>harbour-nextcloudnotes</name>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="104"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="110"/>
<source>Notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="105"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="111"/>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="106"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="112"/>
<source>Synced</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="113"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="119"/>
<source>Error</source>
<translation type="unfinished"></translation>
</message>