NotesModel now directly uses files as backend

This commit is contained in:
Scharel Clemens 2020-05-12 01:30:32 +02:00
parent 964011847c
commit 09c5a68b2f
12 changed files with 160 additions and 710 deletions

View file

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

View file

@ -54,10 +54,8 @@ ApplicationWindow
property bool useCapitalX: value("useCapitalX", false, Boolean)
onCurrentAccountChanged: {
notesProxyModel.sourceModel.clear()
account.path = "/apps/harbour-nextcloudnotes/accounts/" + currentAccount
notesStore.account = currentAccount
notesModel.getAllNotes()
notesModel.account = currentAccount
}
onSortByChanged: {
@ -147,7 +145,7 @@ ApplicationWindow
running: interval > 0 && notesApi.networkAccessible && appWindow.visible
triggeredOnStart: true
onTriggered: {
notesModel.getAllNotes()
notesApi.getAllNotes()
}
onIntervalChanged: {
if (interval > 0) {
@ -156,19 +154,6 @@ ApplicationWindow
}
}
Connections {
target: notesStore
onNoteError: {
storeErrorNotification.close()
if (error) {
console.log("Notes Store error (" + error + "): " + notesStore.errorMessage(error))
storeErrorNotification.body = notesStore.errorMessage(error)
storeErrorNotification.publish()
}
}
}
Connections {
target: notesApi

View file

@ -4,7 +4,6 @@
#include <QObject>
#include "note.h"
#include "notesapi.h"
#include "notesstore.h"
#include "notesmodel.h"
int main(int argc, char *argv[])
@ -27,10 +26,10 @@ int main(int argc, char *argv[])
notesProxyModel->setFilterRole(NotesModel::ContentRole);
notesProxyModel->setSourceModel(notesModel);
NotesStore* notesStore = new NotesStore;
//NotesStore* notesStore = new NotesStore;
NotesApi* notesApi = new NotesApi;
notesModel->setNotesApi(notesApi);
notesModel->setNotesStore(notesStore);
//notesModel->setNotesStore(notesStore);
QQuickView* view = SailfishApp::createView();
#ifdef QT_DEBUG
@ -40,7 +39,7 @@ int main(int argc, char *argv[])
#endif
view->rootContext()->setContextProperty("notesModel", notesModel);
view->rootContext()->setContextProperty("notesProxyModel", notesProxyModel);
view->rootContext()->setContextProperty("notesStore", notesStore);
//view->rootContext()->setContextProperty("notesStore", notesStore);
view->rootContext()->setContextProperty("notesApi", notesApi);
view->setSource(SailfishApp::pathTo("qml/harbour-nextcloudnotes.qml"));
@ -49,7 +48,7 @@ int main(int argc, char *argv[])
int retval = app->exec();
notesApi->deleteLater();
notesStore->deleteLater();
//notesStore->deleteLater();
notesProxyModel->deleteLater();
notesModel->deleteLater();
return retval;

View file

@ -42,17 +42,6 @@ NotesApi::~NotesApi() {
disconnect(&m_manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslError(QNetworkReply*,QList<QSslError>)));
}
QString NotesApi::account() const {
return m_account;
}
void NotesApi::setAccount(const QString &account) {
if (account != m_account) {
m_account = account;
emit accountChanged(account);
}
}
const QList<int> NotesApi::noteIds() {
return m_syncedNotes.keys();
}
@ -612,19 +601,16 @@ void NotesApi::setLoginStatus(LoginStatus status, bool *changed) {
}
void NotesApi::updateApiNotes(const QJsonArray &json) {
QList<int> ids;
for (int i = 0; i < json.size(); ++i) {
if (json[i].isObject()) {
QJsonObject object = json[i].toObject();
if (!object.isEmpty()) {
updateApiNote(json[i].toObject());
ids << object.value("id").toInt(-1);
}
}
}
m_lastSync = QDateTime::currentDateTime();
emit lastSyncChanged(m_lastSync);
emit allNotesChanged(ids);
}
void NotesApi::updateApiNote(const QJsonObject &json) {

View file

@ -182,11 +182,9 @@ signals:
void loginStatusChanged(LoginStatus status);
void loginUrlChanged(QUrl url);
void accountChanged(const QString& account);
void allNotesChanged(const QList<int>& ids);
void noteCreated(const int id, const QJsonObject& note);
void noteUpdated(const int id, const QJsonObject& note);
void noteDeleted(const int id);
void noteCreated(int id, const QJsonObject& note);
void noteUpdated(int id, const QJsonObject& note);
void noteDeleted(int id);
void noteError(ErrorCodes error);
private slots:
@ -199,7 +197,6 @@ private slots:
private:
QUrl m_url;
QString m_account;
QMap<int, int> m_syncedNotes;
QNetworkAccessManager m_manager;
QNetworkRequest m_request;

View file

@ -72,19 +72,25 @@ const QHash<int, QByteArray> NotesModel::m_roleNames = QHash<int, QByteArray> (
{NotesModel::ModifiedStringRole, "modifiedString"},
{NotesModel::NoneRole, "none"} } );
const QString NotesModel::m_fileSuffix = "json";
NotesModel::NotesModel(QObject *parent) : QAbstractListModel(parent) {
mp_notesApi = nullptr;
mp_notesStore = nullptr;
//m_fileDir.setCurrent(directory);
m_fileDir.setPath("");
m_fileDir.setFilter(QDir::Files);
m_fileDir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
m_fileDir.setSorting(QDir::Name | QDir::DirsLast);
m_fileDir.setNameFilters( { "*." + m_fileSuffix } );
}
NotesModel::~NotesModel() {
/*QMapIterator<int, QFile> i(m_files);
while (i.hasNext()) {
i.next();
m_files.take(i.key()).close();
i.toFront();
}*/
setNotesApi(nullptr);
setNotesStore(nullptr);
clear();
}
void NotesModel::setNotesApi(NotesApi *notesApi) {
@ -98,31 +104,13 @@ void NotesModel::setNotesApi(NotesApi *notesApi) {
mp_notesApi = notesApi;
if (mp_notesApi) {
// connect stuff
connect(mp_notesApi, SIGNAL(accountChanged(QString)), this, SIGNAL(accountChanged(QString)));
//connect(mp_notesApi, SIGNAL(accountChanged(QString)), this, SIGNAL(accountChanged(QString)));
connect(mp_notesApi, SIGNAL(noteCreated(int,QJsonObject)), this, SLOT(insert(int,QJsonObject)));
connect(mp_notesApi, SIGNAL(noteUpdated(int,QJsonObject)), this, SLOT(update(int,QJsonObject)));
connect(mp_notesApi, SIGNAL(noteDeleted(int)), this, SLOT(remove(int)));
}
}
void NotesModel::setNotesStore(NotesStore *notesStore) {
if (mp_notesStore) {
// disconnect stuff
disconnect(mp_notesStore, SIGNAL(accountChanged(QString)), this, SIGNAL(accountChanged(QString)));
disconnect(mp_notesStore, SIGNAL(noteCreated(int,QJsonObject)), this, SLOT(insert(int,QJsonObject)));
disconnect(mp_notesStore, SIGNAL(noteUpdated(int,QJsonObject)), this, SLOT(update(int,QJsonObject)));
disconnect(mp_notesStore, SIGNAL(noteDeleted(int)), this, SLOT(remove(int)));
}
mp_notesStore = notesStore;
if (mp_notesStore) {
// connect stuff
connect(mp_notesStore, SIGNAL(accountChanged(QString)), this, SIGNAL(accountChanged(QString)));
connect(mp_notesStore, SIGNAL(noteCreated(int,QJsonObject)), this, SLOT(insert(int,QJsonObject)));
connect(mp_notesStore, SIGNAL(noteUpdated(int,QJsonObject)), this, SLOT(update(int,QJsonObject)));
connect(mp_notesStore, SIGNAL(noteDeleted(int)), this, SLOT(remove(int)));
}
}
QString NotesModel::account() const {
if (m_fileDir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
return m_fileDir.path();
@ -132,6 +120,7 @@ QString NotesModel::account() const {
void NotesModel::setAccount(const QString& account) {
qDebug() << "Setting account: " << account;
beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
if (account != m_fileDir.path()) {
if (m_fileDir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
m_fileDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
@ -139,6 +128,9 @@ void NotesModel::setAccount(const QString& account) {
if (!account.isEmpty()) {
m_fileDir.setPath(account);
if (m_fileDir.mkpath(".")) {
m_fileDir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
m_fileDir.setSorting(QDir::Name | QDir::DirsLast);
m_fileDir.setNameFilters( { "*." + m_fileSuffix } );
emit accountChanged(m_fileDir.path());
}
else {
@ -149,6 +141,10 @@ void NotesModel::setAccount(const QString& account) {
}
//qDebug() << account << m_dir.path();
}
endRemoveRows();
beginInsertRows(QModelIndex(), 0, rowCount() - 1);
qDebug() << rowCount() << " notes in account";
endInsertRows();
}
/*
const QList<int> NotesModel::noteIds() {
@ -179,181 +175,134 @@ int NotesModel::noteModified(const int id) {
return Note::modified(QJsonObject::fromVariantMap(getNoteById(id)));
}
*/
int NotesModel::newNotePosition(const int id) const {
if (m_fileDir.exists() && !account().isEmpty() && id >= 0) {
QStringList fileList = m_fileDir.entryList();
qDebug() << fileList;
fileList << QString("%1.%2").arg(id).arg(m_fileSuffix);
fileList.sort(Qt::CaseInsensitive);
qDebug() << fileList;
return fileList.indexOf(QString("%1.%2").arg(id).arg(m_fileSuffix));
}
return -1;
}
const QVariantMap NotesModel::note(const int id) const {
QVariantMap json;
QFileInfo fileinfo(m_fileDir, QString("%1.%2").arg(id).arg(m_fileSuffix));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
json = QJsonDocument::fromJson(file.readAll()).object().toVariantMap();
file.close();
if (m_fileDir.exists() && !account().isEmpty() && id >= 0) {
QFileInfo fileinfo(m_fileDir, QString("%1.%2").arg(id).arg(m_fileSuffix));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
json = QJsonDocument::fromJson(file.readAll()).object().toVariantMap();
file.close();
}
}
else {
//emit noteError(FileCannotReadError);
}
}
else {
//emit noteError(FileNotFoundError);
}
return json;
}
bool NotesModel::setNote(const QVariantMap &note, int id) const {
bool NotesModel::setNote(const QVariantMap &note, int id) {
bool ok;
if (id < 0) {
id = note.value(m_roleNames[IdRole]).toInt(&ok);
if (!ok) id = -1;
}
else {
ok = true;
}
if (id >= 0 && ok) {
ok = false;
ok = false;
if (m_fileDir.exists() && !account().isEmpty() && id >= 0) {
QFileInfo fileinfo(m_fileDir, QString("%1.%2").arg(id).arg(m_fileSuffix));
QFile file(fileinfo.filePath());
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QByteArray data = QJsonDocument(QJsonObject::fromVariantMap(note)).toJson();
if (file.write(data) == data.size()) {
ok = true;
}
file.close();
}
}
return ok;
}
bool NotesModel::getAllNotes(const QStringList &exclude) {
bool success = true;
if (mp_notesApi)
success &= mp_notesApi->getAllNotes(exclude);
if (mp_notesStore)
success &= mp_notesStore->getAllNotes(exclude);
return success;
}
bool NotesModel::getNote(const int id, const QStringList &exclude) {
bool success = true;
if (mp_notesApi)
success &= mp_notesApi->getNote(id, exclude);
if (mp_notesStore)
success &= mp_notesStore->getNote(id, exclude);
return success;
}
bool NotesModel::createNote(const QJsonObject &note) {
bool success = true;
if (mp_notesApi)
success &= mp_notesApi->createNote(note);
return success;
}
bool NotesModel::updateNote(const int id, const QJsonObject &note) {
bool success = true;
if (mp_notesApi)
success &= mp_notesApi->updateNote(id, note);
if (mp_notesStore)
success &= mp_notesStore->updateNote(id, note);
return success;
}
bool NotesModel::deleteNote(const int id) {
bool success = true;
if (mp_notesApi)
success &= mp_notesApi->deleteNote(id);
if (mp_notesStore)
success &= mp_notesStore->deleteNote(id);
return success;
}
bool NotesModel::syncNotes() {
if (mp_notesApi && mp_notesStore) {
// TODO
if (m_fileDir.exists() && !account().isEmpty() && id >= 0) {
QFileInfo fileinfo(m_fileDir, QString("%1.%2").arg(id).arg(m_fileSuffix));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.remove()) {
return true;
}
}
}
return false;
}
void NotesModel::insert(const int id, const QJsonObject& note) {
qDebug() << "Inserting note: " << id;
if (m_notes.contains(id)) {
qDebug() << "Note already present";
update(id, note);
void NotesModel::insert(int id, const QJsonObject& json) {
if (id < 0) {
id = json.value(m_roleNames[IdRole]).toInt(-1);
}
else {
beginInsertRows(QModelIndex(), indexOfNoteById(id), indexOfNoteById(id));
m_notes.insert(id, note);
endInsertRows();
//emit noteInserted(id, note);
qDebug() << "Note inserted";
}
if (mp_notesApi && mp_notesStore) {
if (sender() == mp_notesApi) {
if (!mp_notesStore->noteExists(id)) {
mp_notesStore->createNote(note);
}
if (id >= 0) {
qDebug() << "Inserting note: " << id;
if (indexOfNoteById(id) >= 0) {
qDebug() << "Note already present";
update(id, json);
}
else {
qDebug() << "New position: " << newNotePosition(id);
beginInsertRows(QModelIndex(), newNotePosition(id), newNotePosition(id));
setNote(json.toVariantMap(), id);
endInsertRows();
//emit noteInserted(id, note);
qDebug() << "Note inserted";
}
}
}
void NotesModel::update(const int id, const QJsonObject &note) {
qDebug() << "Updating note: " << id;
if (!m_notes.contains(id)) {
qDebug() << "Note is new";
insert(id, note);
void NotesModel::update(int id, const QJsonObject &json) {
if (id < 0) {
id = json.value(m_roleNames[IdRole]).toInt(-1);
}
else {
if (m_notes.value(id) == note) {
qDebug() << "Note unchanged";
if (id >= 0) {
qDebug() << "Updating note: " << id;
if (indexOfNoteById(id) < 0) {
qDebug() << "Note is new";
insert(id, json);
}
else {
m_notes.insert(id, note);
setNote(json.toVariantMap(), id);
emit dataChanged(index(indexOfNoteById(id)), index(indexOfNoteById(id)));
emit noteUpdated(id, note);
qDebug() << "Note changed";
}
}
if (mp_notesApi && mp_notesStore) {
if (sender() == mp_notesApi) {
if (Note::modified(note) > mp_notesStore->noteModified(id)) {
mp_notesStore->updateNote(id, note);
}
}
if (sender() == mp_notesStore) {
if (Note::modified(note) > mp_notesApi->noteModified(id) && !mp_notesApi->lastSync().isNull()) {
mp_notesApi->updateNote(id, note);
}
}
}
}
void NotesModel::remove(const int id) {
void NotesModel::remove(int id) {
qDebug() << "Removing note: " << id;
if (m_notes.contains(id)) {
if (indexOfNoteById(id) >= 0) {
beginRemoveRows(QModelIndex(), indexOfNoteById(id), indexOfNoteById(id));
if (m_notes.remove(id) > 0) {
emit noteDeleted(id);
}
else {
qDebug() << "Note not found";
}
deleteNote(id);
endRemoveRows();
}
if (mp_notesApi && mp_notesStore) {
if (sender() == mp_notesApi) {
mp_notesStore->deleteNote(id);
}
if (sender() == mp_notesStore) {
mp_notesApi->deleteNote(id);
}
qDebug() << "Note removed";
}
}
void NotesModel::clear() {
qDebug() << "Clearing model";
beginResetModel();
m_notes.clear();
endResetModel();
int NotesModel::indexOfNoteById(int id) const {
int index = -1;
if (m_fileDir.exists() && !account().isEmpty()) {
index = m_fileDir.entryList().indexOf(QRegExp(QString("^%1.%2$").arg(id).arg(m_fileSuffix)));
}
return index;
}
int NotesModel::indexOfNoteById(int id) const {
return std::distance(m_notes.begin(), m_notes.lowerBound(id));
int NotesModel::idOfNoteByINdex(int index) const {
int id = -1;
if (m_fileDir.exists() && !account().isEmpty()) {
QFileInfo fileName = m_fileDir.entryInfoList().value(index);
bool ok;
id = fileName.baseName().toInt(&ok);
if (!ok) id = -1;
}
return id;
}
QHash<int, QByteArray> NotesModel::roleNames() const {
@ -374,7 +323,7 @@ Qt::ItemFlags NotesModel::flags(const QModelIndex &index) const {
}
int NotesModel::rowCount(const QModelIndex &parent) const {
if (parent.column() == 0 && m_fileDir.exists() && !account().isEmpty()) {
if (m_fileDir.exists() && !account().isEmpty()) {
return static_cast<int>(m_fileDir.count());
}
else {
@ -382,58 +331,39 @@ int NotesModel::rowCount(const QModelIndex &parent) const {
}
}
QVariant NotesModel::data(const QModelIndex &index, int role) {
if (role == ModifiedStringRole)
QVariant NotesModel::data(const QModelIndex &index, int role) const {
return itemData(index).value(role);
}
bool NotesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
return setItemData(index, QMap<int, QVariant>{ { role, value } } );
if (setItemData(index, QMap<int, QVariant>{ { role, value } } )) {
emit dataChanged(index, index);
return true;
}
return false;
}
QMap<int, QVariant> NotesModel::itemData(const QModelIndex &index) {
QMap<int, QVariant> NotesModel::itemData(const QModelIndex &index) const {
QMap<int, QVariant> map;
if (index.isValid() && index.row() < m_files.size()) {
QMap<int, QFile>::iterator i = m_files.begin();
i += index.row();
if (i.value().isReadable()) {
QJsonObject json = QJsonDocument::fromJson(i.value().readAll()).object();
for (int role = IdRole; role <= ErrorMessageRole; ++role) {
map.insert(role, json.value(m_roleNames[role]));
}
}
else {
qDebug() << "File not readable: " << i.value().fileName();
if (index.isValid() && index.row() < rowCount()) {
QVariantMap note = this->note(idOfNoteByINdex(index.row()));
for (int role = IdRole; role <= ErrorMessageRole; ++role) {
map.insert(role, note.value(m_roleNames[role]));
}
}
return map;
}
bool NotesModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles) {
if (index.isValid() && index.row() < m_files.size()) {
QMap<int, QFile>::iterator i = m_files.begin();
i += index.row();
if (i.value().isReadable() && i.value().isWritable()) {
QJsonObject json = QJsonDocument::fromJson(i.value().readAll()).object();
QMapIterator<int, QVariant> i(roles);
while (i.hasNext()) {
i.next();
json.insert(m_roleNames[i.key()], QJsonValue::fromVariant(i.value()));
}
}
QVariantMap note;
if (index.isValid() && index.row() < rowCount()) {
note = this->note(idOfNoteByINdex(index.row()));
QMapIterator<int, QVariant> i(roles);
while (i.hasNext()) {
i.next();
note.insert(m_roleNames[i.key()], i.value());
}
else {
qDebug() << "File not writable: " << i.value().fileName();
}
//qDebug();
bool retval = true;
QMapIterator<int, QVariant> role(roles);
while (role.hasNext()) {
role.next();
retval &= setData(index, role.value(), role.key());
return setNote(note, idOfNoteByINdex(index.row()));
}
return retval;
return false;
}

View file

@ -3,12 +3,13 @@
#include <QSortFilterProxyModel>
#include <QAbstractListModel>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QJsonArray>
#include <QDateTime>
#include "note.h"
#include "notesapi.h"
#include "notesstore.h"
class NotesProxyModel : public QSortFilterProxyModel {
Q_OBJECT
@ -50,6 +51,10 @@ public:
explicit NotesModel(QObject *parent = nullptr);
virtual ~NotesModel();
Q_PROPERTY(QString account READ account WRITE setAccount NOTIFY accountChanged)
QString account() const;
void setAccount(const QString& account);
enum NoteRoles {
IdRole = Qt::UserRole,
ModifiedRole = Qt::UserRole + 1,
@ -68,57 +73,41 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role);
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual QVariant data(const QModelIndex &index, int role) const;
//virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
QMap<int, QVariant> itemData(const QModelIndex &index);
//virtual bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
//virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
virtual QMap<int, QVariant> itemData(const QModelIndex &index) const;
virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles);
void setNotesApi(NotesApi* notesApi);
void setNotesStore(NotesStore *notesStore);
QString account() const;
void setAccount(const QString& account);
int newNotePosition(const int id) const;
Q_INVOKABLE const QVariantMap note(const int id) const;
Q_INVOKABLE bool setNote(const QVariantMap& note, int id = -1) const;
Q_INVOKABLE bool setNote(const QVariantMap& note, int id = -1);
Q_INVOKABLE bool deleteNote(const int id);
public slots:
Q_INVOKABLE bool getAllNotes(const QStringList& exclude = QStringList());
Q_INVOKABLE bool getNote(const int id, const QStringList& exclude = QStringList());
Q_INVOKABLE bool createNote(const QJsonObject& note);
Q_INVOKABLE bool updateNote(const int id, const QJsonObject& note);
Q_INVOKABLE bool deleteNote(const int id);
Q_INVOKABLE bool syncNotes();
void insert(int id, const QJsonObject& json);
void update(int id, const QJsonObject& json);
void remove(int id);
void insert(const int id, const QJsonObject& note);
void update(const int id, const QJsonObject& note);
void remove(const int id);
Q_INVOKABLE void clear();
Q_INVOKABLE int indexOfNoteById(int id) const;
Q_INVOKABLE int idOfNoteByINdex(int index) const;
signals:
void accountChanged(const QString& account);
void allNotesChanged(const QList<int>& ids);
void noteCreated(const int id, const QJsonObject& note);
void noteUpdated(const int id, const QJsonObject& note);
void noteDeleted(const int id);
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int> ());
private:
QMap<int, QJsonObject> m_notes;
const static QHash<int, QByteArray> m_roleNames;
QMap<int, QFile> m_files;
//QMap<int, QFile> m_files;
QDir m_fileDir;
const static QString m_fileSuffix;
NotesApi* mp_notesApi;
NotesStore* mp_notesStore;
};
#endif // NOTESMODEL_H

View file

@ -1,256 +0,0 @@
#include "notesstore.h"
#include "note.h"
#include <QJsonDocument>
#include <QDateTime>
#include <QDebug>
const QString NotesStore::m_suffix = "json";
NotesStore::NotesStore(QString directory, QObject *parent) : QObject(parent)
{
m_dir.setCurrent(directory);
m_dir.setPath("");
m_dir.setFilter(QDir::Files);
m_dir.setNameFilters( { "*." + m_suffix } );
}
NotesStore::~NotesStore() {
}
QString NotesStore::account() const {
if (m_dir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
return m_dir.path();
}
return QString();
}
void NotesStore::setAccount(const QString& account) {
qDebug() << "Setting account: " << account;
if (account != m_dir.path()) {
if (m_dir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
m_dir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
}
if (!account.isEmpty()) {
m_dir.setPath(account);
if (m_dir.mkpath(".")) {
emit accountChanged(m_dir.path());
}
else {
qDebug() << "Failed to create or already present: " << m_dir.path();
m_dir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
emit noteError(DirCannotWriteError);
}
}
//qDebug() << account << m_dir.path();
}
}
const QList<int> NotesStore::noteIds() {
QList<int> ids;
if (m_dir.exists() && !account().isEmpty()) {
QFileInfoList files = m_dir.entryInfoList();
for (int i = 0; i < files.size(); ++i) {
bool ok;
int id = files[i].baseName().toInt(&ok);
if (ok) {
ids << id;
}
}
}
else {
qDebug() << errorMessage(DirNotFoundError);
emit noteError(DirCannotReadError);
}
return ids;
}
bool NotesStore::noteExists(const int id) {
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
return fileinfo.exists();
}
int NotesStore::noteModified(const int id) {
return Note::modified(readNoteFile(id, { "content" }));
}
const QString NotesStore::errorMessage(ErrorCodes error) const {
QString message;
switch (error) {
case NoError:
message = tr("No error");
break;
case FileNotFoundError:
message = tr("File not found");
break;
case FileCannotReadError:
message = tr("Cannot read from the file");
break;
case FileCannotWriteError:
message = tr("Cannot write to the file");
break;
case DirNotFoundError:
message = tr("Directory not found");
break;
case DirCannotReadError:
message = tr("Cannot read from directory");
break;
case DirCannotWriteError:
message = tr("Cannot create or write to directory");
break;
default:
message = tr("Unknown error");
break;
}
return message;
}
QJsonObject NotesStore::readNoteFile(const int id, const QStringList& exclude) {
QJsonObject json;
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QByteArray data = file.readAll();
json = QJsonDocument::fromJson(data).object();
file.close();
for (int i = 0; i < exclude.size(); ++i) {
json.remove(exclude[i]);
}
}
else {
emit noteError(FileCannotReadError);
}
}
else {
//emit noteError(FileNotFoundError);
}
return json;
}
bool NotesStore::writeNoteFile(const int id, const QJsonObject& note) {
bool success = false;
if (!account().isEmpty()) {
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
QFile file(fileinfo.filePath());
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
QByteArray data = QJsonDocument(note).toJson();
if (file.write(data) == data.size()) {
success = true;
}
file.close();
}
else {
emit noteError(FileCannotWriteError);
}
}
return success;
}
bool NotesStore::removeNoteFile(const int id) {
bool success = false;
if (!account().isEmpty()) {
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
QFile file(fileinfo.filePath());
if (file.exists()) {
if (file.remove()) {
success = true;
}
else {
emit noteError(FileCannotWriteError);
}
}
else {
emit noteError(FileNotFoundError);
}
}
return success;
}
bool NotesStore::getAllNotes(const QStringList& exclude) {
qDebug() << "Getting all notes";
const QList<int> ids = noteIds();
if (!ids.empty()) {
for (int i = 0; i < ids.size(); ++i) {
getNote(ids.at(i), exclude);
}
return true;
}
else {
return false;
}
}
bool NotesStore::getNote(const int id, const QStringList& exclude) {
qDebug() << "Getting note: " << id;
QJsonObject note;
if (id >= 0) {
note = readNoteFile(id, exclude);
emit noteUpdated(id, note);
return true;
}
else {
qDebug() << "Skipping, invalid ID";
}
return false;
}
bool NotesStore::createNote(const QJsonObject& note) {
int id = note.value("id").toInt(-1);
qDebug() << "Creating note: " << id;
if (id < 0) {
// TODO probably crate files with an '.json.<NUMBER>.new' extension
qDebug() << "Creating notes without the server API is not supported yet!";
}
else if (!noteExists(id)) {
if (writeNoteFile(id, note)) {
emit noteUpdated(id, note);
return true;
}
}
else {
qDebug() << "Note already exists";
}
return false;
}
bool NotesStore::updateNote(const int id, const QJsonObject& note) {
qDebug() << "Updating note: " << id;
if (id >= 0) {
QJsonObject tmpNote = readNoteFile(id);
if (note != tmpNote) {
if (note.value("modified").toInt() >= tmpNote.value("modified").toInt() || note.value("modified").toInt() == 0) {
QStringList fields = note.keys();
for (int i = 0; i < fields.size(); ++i) {
tmpNote[fields[i]] = note[fields[i]];
}
if (tmpNote.value("modified").toInt() == 0) {
tmpNote["modified"] = QJsonValue::fromVariant(QDateTime::currentDateTime().toTime_t());
}
if (writeNoteFile(id, tmpNote)) {
emit noteUpdated(id, tmpNote);
return true;
}
}
else {
qDebug() << "Skipping, note is older" << QDateTime::fromTime_t(note.value("modified").toInt()) << QDateTime::fromTime_t(tmpNote.value("modified").toInt());
}
}
else {
qDebug() << "Skipping, note is equal";
}
}
else {
qDebug() << "Skipping, invalid ID";
}
return false;
}
bool NotesStore::deleteNote(const int id) {
qDebug() << "Deleting note: " << id;
if (removeNoteFile(id)) {
emit noteDeleted(id);
return true;
}
return false;
}

View file

@ -1,65 +0,0 @@
#ifndef NOTESSTORE_H
#define NOTESSTORE_H
#include <QObject>
#include <QJsonArray>
#include <QJsonObject>
#include <QDir>
#include <QStandardPaths>
class NotesStore : public QObject
{
Q_OBJECT
public:
explicit NotesStore(
QString directory = QStandardPaths::writableLocation(QStandardPaths::DataLocation),
QObject *parent = nullptr);
virtual ~NotesStore();
Q_PROPERTY(QString account READ account WRITE setAccount NOTIFY accountChanged)
QString account() const;
void setAccount(const QString& account);
const QList<int> noteIds();
bool noteExists(const int id);
int noteModified(const int id);
enum ErrorCodes {
NoError,
FileNotFoundError,
FileCannotReadError,
FileCannotWriteError,
DirNotFoundError,
DirCannotReadError,
DirCannotWriteError
};
Q_ENUM(ErrorCodes)
Q_INVOKABLE const QString errorMessage(ErrorCodes error) const;
QJsonObject readNoteFile(const int id, const QStringList& exclude = QStringList());
bool writeNoteFile(const int id, const QJsonObject& note);
bool removeNoteFile(const int id);
public slots:
Q_INVOKABLE bool getAllNotes(const QStringList& exclude = QStringList());
Q_INVOKABLE bool getNote(const int id, const QStringList& exclude = QStringList());
Q_INVOKABLE bool createNote(const QJsonObject& note);
Q_INVOKABLE bool updateNote(const int id, const QJsonObject& note);
Q_INVOKABLE bool deleteNote(const int id);
signals:
void accountChanged(const QString& account);
void allNotesChanged(const QList<int>& ids);
void noteCreated(const int id, const QJsonObject& note);
void noteUpdated(const int id, const QJsonObject& note);
void noteDeleted(const int id);
void noteError(const ErrorCodes error);
private:
QDir m_dir;
const static QString m_suffix;
};
#endif // NOTESSTORE_H

View file

@ -362,41 +362,6 @@
<translation>Ein Fehler ist aufgetreten</translation>
</message>
</context>
<context>
<name>NotesStore</name>
<message>
<source>File not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot read from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot write to the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Directory not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot read from directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot create or write to directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unknown error</source>
<translation type="unfinished">Unbekannter Fehler</translation>
</message>
<message>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>
<message>

View file

@ -362,41 +362,6 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NotesStore</name>
<message>
<source>File not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot read from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot write to the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Directory not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot read from directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot create or write to directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>
<message>

View file

@ -316,32 +316,32 @@
<context>
<name>NotesApi</name>
<message>
<location filename="../src/notesapi.cpp" line="336"/>
<location filename="../src/notesapi.cpp" line="325"/>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="339"/>
<location filename="../src/notesapi.cpp" line="328"/>
<source>No network connection available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="342"/>
<location filename="../src/notesapi.cpp" line="331"/>
<source>Failed to communicate with the Nextcloud server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="345"/>
<location filename="../src/notesapi.cpp" line="334"/>
<source>An error occured while establishing an encrypted connection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="348"/>
<location filename="../src/notesapi.cpp" line="337"/>
<source>Could not authenticate to the Nextcloud instance</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="351"/>
<location filename="../src/notesapi.cpp" line="340"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
@ -444,49 +444,6 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>NotesStore</name>
<message>
<location filename="../src/notesstore.cpp" line="81"/>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="84"/>
<source>File not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="87"/>
<source>Cannot read from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="90"/>
<source>Cannot write to the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="93"/>
<source>Directory not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="96"/>
<source>Cannot read from directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="99"/>
<source>Cannot create or write to directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="102"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsPage</name>
<message>
@ -883,27 +840,27 @@ You can also use other markdown syntax inside them.</source>
<context>
<name>harbour-nextcloudnotes</name>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="123"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="121"/>
<source>Notes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="124"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="122"/>
<source>Offline</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="125"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="123"/>
<source>Synced</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="139"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="137"/>
<source>API error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="132"/>
<location filename="../qml/harbour-nextcloudnotes.qml" line="130"/>
<source>File error</source>
<translation type="unfinished"></translation>
</message>