Improved communication between the modules. Offline mode seems to work fine now. Some kind of synchronisation of offline changes is now needed

This commit is contained in:
Scharel Clemens 2020-04-13 19:13:14 +02:00
parent 629d752dee
commit 4ff667e3d1
15 changed files with 432 additions and 224 deletions

View file

@ -129,9 +129,16 @@ ApplicationWindow
} }
Notification { Notification {
id: errorNotification id: storeErrorNotification
appName: offlineNotification.appName appName: offlineNotification.appName
summary: qsTr("Error") summary: qsTr("File error")
Component.onDestruction: close()
}
Notification {
id: apiErrorNotification
appName: offlineNotification.appName
summary: qsTr("API error")
Component.onDestruction: close() Component.onDestruction: close()
} }
@ -157,6 +164,19 @@ 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 { Connections {
target: notesApi target: notesApi
@ -164,18 +184,24 @@ ApplicationWindow
console.log("Device is " + (accessible ? "online" : "offline")) console.log("Device is " + (accessible ? "online" : "offline"))
accessible ? offlineNotification.close(Notification.Closed) : offlineNotification.publish() accessible ? offlineNotification.close(Notification.Closed) : offlineNotification.publish()
} }
onError: { onNoteError: {
apiErrorNotification.close()
if (error) if (error)
console.log("Error (" + error + "): " + notesApi.errorMessage(error)) console.log("Notes API error (" + error + "): " + notesApi.errorMessage(error))
errorNotification.close()
if (error && notesApi.networkAccessible) { if (error && notesApi.networkAccessible) {
errorNotification.body = notesApi.errorMessage(error) apiErrorNotification.body = notesApi.errorMessage(error)
errorNotification.publish() apiErrorNotification.publish()
} }
} }
onLastSyncChanged: account.update = lastSync onLastSyncChanged: account.update = lastSync
} }
Component.onDestruction: {
offlineNotification.close()
storeErrorNotification.close()
apiErrorNotification.close()
}
initialPage: Component { NotesPage { } } initialPage: Component { NotesPage { } }
cover: Qt.resolvedUrl("cover/CoverPage.qml") cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: defaultAllowedOrientations allowedOrientations: defaultAllowedOrientations

View file

@ -120,6 +120,7 @@ Page {
icon.source: (favorite ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") + icon.source: (favorite ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") +
(note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor) (note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor)
onClicked: { onClicked: {
console.log("Setting favoirte: " + !favorite)
notesStore.updateNote(id, {'favorite': !favorite, 'modified': new Date().valueOf() / 1000 } ) notesStore.updateNote(id, {'favorite': !favorite, 'modified': new Date().valueOf() / 1000 } )
notesApi.updateNote(id, {'favorite': !favorite, 'modified': new Date().valueOf() / 1000 } ) notesApi.updateNote(id, {'favorite': !favorite, 'modified': new Date().valueOf() / 1000 } )
} }

View file

@ -30,15 +30,15 @@ int main(int argc, char *argv[])
NotesStore* notesStore = new NotesStore; NotesStore* notesStore = new NotesStore;
NotesApi* notesApi = new NotesApi; NotesApi* notesApi = new NotesApi;
QObject::connect(notesApi, SIGNAL(noteUpdated(Note)), notesStore, SLOT(updateNote(Note))); QObject::connect(notesApi, SIGNAL(noteUpdated(int, QJsonObject)), notesStore, SLOT(updateNote(int, QJsonObject)));
//QObject::connect(notesStore, SIGNAL(noteUpdated(Note)), notesApi, SLOT(updateNote(Note))); //QObject::connect(notesStore, SIGNAL(noteUpdated(Note)), notesApi, SLOT(updateNote(Note)));
QObject::connect(notesApi, SIGNAL(noteDeleted(int)), notesStore, SLOT(deleteNote(int))); QObject::connect(notesApi, SIGNAL(noteDeleted(int)), notesStore, SLOT(deleteNote(int)));
//QObject::connect(notesStore, SIGNAL(noteDeleted(int)), notesApi, SLOT(deleteNote(int))); //QObject::connect(notesStore, SIGNAL(noteDeleted(int)), notesApi, SLOT(deleteNote(int)));
QObject::connect(notesStore, SIGNAL(noteUpdated(Note)), notesModel, SLOT(insertNote(Note))); QObject::connect(notesStore, SIGNAL(noteUpdated(int, QJsonObject)), notesModel, SLOT(insertNote(int, QJsonObject)));
QObject::connect(notesStore, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int))); QObject::connect(notesStore, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int)));
QObject::connect(notesApi, SIGNAL(noteUpdated(Note)), notesModel, SLOT(insertNote(Note))); //QObject::connect(notesApi, SIGNAL(noteUpdated(Note)), notesModel, SLOT(insertNote(Note)));
QObject::connect(notesApi, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int))); //QObject::connect(notesApi, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int)));
QQuickView* view = SailfishApp::createView(); QQuickView* view = SailfishApp::createView();
#ifdef QT_DEBUG #ifdef QT_DEBUG
@ -54,15 +54,15 @@ int main(int argc, char *argv[])
view->show(); view->show();
int retval = app->exec(); int retval = app->exec();
QObject::disconnect(notesApi, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int))); //QObject::disconnect(notesApi, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int)));
QObject::disconnect(notesApi, SIGNAL(noteUpdated(Note)), notesModel, SLOT(insertNote(Note))); //QObject::disconnect(notesApi, SIGNAL(noteUpdated(Note)), notesModel, SLOT(insertNote(Note)));
QObject::disconnect(notesStore, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int))); QObject::disconnect(notesStore, SIGNAL(noteDeleted(int)), notesModel, SLOT(removeNote(int)));
QObject::disconnect(notesStore, SIGNAL(noteUpdated(Note)), notesModel, SLOT(insertNote(Note))); QObject::disconnect(notesStore, SIGNAL(noteUpdated(int, QJsonObject)), notesModel, SLOT(insertNote(int, QJsonObject)));
//QObject::disconnect(notesStore, SIGNAL(noteDeleted(int)), notesApi, SLOT(deleteNote(int))); //QObject::disconnect(notesStore, SIGNAL(noteDeleted(int)), notesApi, SLOT(deleteNote(int)));
QObject::disconnect(notesApi, SIGNAL(noteDeleted(int)), notesStore, SLOT(deleteNote(int))); QObject::disconnect(notesApi, SIGNAL(noteDeleted(int)), notesStore, SLOT(deleteNote(int)));
//QObject::disconnect(notesStore, SIGNAL(noteUpdated(Note)), notesApi, SLOT(updateNote(Note))); //QObject::disconnect(notesStore, SIGNAL(noteUpdated(Note)), notesApi, SLOT(updateNote(Note)));
QObject::disconnect(notesApi, SIGNAL(noteUpdated(Note)), notesStore, SLOT(updateNote(Note))); QObject::disconnect(notesApi, SIGNAL(noteUpdated(int, QJsonObject)), notesStore, SLOT(updateNote(int, QJsonObject)));
notesApi->deleteLater(); notesApi->deleteLater();
notesStore->deleteLater(); notesStore->deleteLater();

View file

@ -70,6 +70,28 @@ Note& Note::operator =(const QJsonObject& note) {
return *this; return *this;
} }
Note& Note::operator <<(const QJsonObject& note) {
if (note.contains(Note::noteFieldName(Note::Id)))
setId(id(note));
if (note.contains(Note::noteFieldName(Note::Modified)))
setModified(modified(note));
if (note.contains(Note::noteFieldName(Note::Title)))
setTitle(title(note));
if (note.contains(Note::noteFieldName(Note::Category)))
setCategory(category(note));
if (note.contains(Note::noteFieldName(Note::Content)))
setContent(content(note));
if (note.contains(Note::noteFieldName(Note::Favorite)))
setFavorite(favorite(note));
if (note.contains(Note::noteFieldName(Note::Etag)))
setEtag(etag(note));
if (note.contains(Note::noteFieldName(Note::Error)))
setError(error(note));
if (note.contains(Note::noteFieldName(Note::ErrorMessage)))
setErrorMessage(errorMessage(note));
return *this;
}
bool Note::operator ==(const Note& note) const { bool Note::operator ==(const Note& note) const {
return id() == note.id(); return id() == note.id();
} }

View file

@ -19,6 +19,7 @@ public:
Note& operator =(const Note& note); Note& operator =(const Note& note);
Note& operator =(const QJsonObject& note); Note& operator =(const QJsonObject& note);
Note& operator <<(const QJsonObject& note);
bool operator ==(const Note& note) const; bool operator ==(const Note& note) const;
bool operator ==(const QJsonObject& note) const; bool operator ==(const QJsonObject& note) const;
bool equal(const Note& note) const; bool equal(const Note& note) const;

View file

@ -50,24 +50,12 @@ void NotesApi::setAccount(const QString &account) {
} }
} }
void NotesApi::getAllNotes(const QStringList exclude) { void NotesApi::getAllNotes(const QStringList& exclude) {
getAllNotes(Note::noteFieldsFromStringList(exclude));
}
void NotesApi::getAllNotes(Note::NoteField exclude) {
qDebug() << "Getting all notes"; qDebug() << "Getting all notes";
QUrl url = apiEndpointUrl(m_notesEndpoint); QUrl url = apiEndpointUrl(m_notesEndpoint);
QStringList excludeFields;
QList<Note::NoteField> noteFields = Note::noteFields();
QFlags<Note::NoteField> flags(exclude);
for (int i = 0; i < noteFields.size(); ++i) { if (!exclude.isEmpty())
if (flags.testFlag(noteFields[i])) { url.setQuery(QString(EXCLUDE_QUERY).append(exclude.join(",")));
excludeFields << Note::noteFieldName(noteFields[i]);
}
}
if (!excludeFields.isEmpty())
url.setQuery(QString(EXCLUDE_QUERY).append(excludeFields.join(",")));
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "GET" << url.toDisplayString(); qDebug() << "GET" << url.toDisplayString();
@ -77,24 +65,11 @@ void NotesApi::getAllNotes(Note::NoteField exclude) {
} }
} }
void NotesApi::getNote(const int id, const QStringList exclude) { void NotesApi::getNote(const int id, const QStringList& exclude) {
getNote(id, Note::noteFieldsFromStringList(exclude));
}
void NotesApi::getNote(const int id, Note::NoteField exclude) {
qDebug() << "Getting note: " << id; qDebug() << "Getting note: " << id;
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id)); QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
QStringList excludeFields; if (!exclude.isEmpty())
QList<Note::NoteField> noteFields = Note::noteFields(); url.setQuery(QString(EXCLUDE_QUERY).append(exclude.join(",")));
QFlags<Note::NoteField> flags(exclude);
for (int i = 0; i < noteFields.size(); ++i) {
if (flags.testFlag(noteFields[i])) {
excludeFields << Note::noteFieldName(noteFields[i]);
}
}
if (!excludeFields.isEmpty())
url.setQuery(QString(EXCLUDE_QUERY).append(excludeFields.join(",")));
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "GET" << url.toDisplayString(); qDebug() << "GET" << url.toDisplayString();
@ -104,34 +79,24 @@ void NotesApi::getNote(const int id, Note::NoteField exclude) {
} }
} }
void NotesApi::createNote(const QVariantMap &note) { void NotesApi::createNote(const QJsonObject& note) {
createNote(Note(QJsonObject::fromVariantMap(note)));
}
void NotesApi::createNote(const Note &note) {
qDebug() << "Creating note"; qDebug() << "Creating note";
QUrl url = apiEndpointUrl(m_notesEndpoint); QUrl url = apiEndpointUrl(m_notesEndpoint);
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) { if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "POST" << url.toDisplayString(); qDebug() << "POST" << url.toDisplayString();
m_authenticatedRequest.setUrl(url); m_authenticatedRequest.setUrl(url);
m_createNoteReplies << m_manager.post(m_authenticatedRequest, noteApiData(note)); m_createNoteReplies << m_manager.post(m_authenticatedRequest, QJsonDocument(note).toJson());
emit busyChanged(true); emit busyChanged(true);
} }
} }
void NotesApi::updateNote(const int id, const QVariantMap &note) { void NotesApi::updateNote(const int id, const QJsonObject& note) {
Note newNote(QJsonObject::fromVariantMap(note)); qDebug() << "Updating note: " << id;
newNote.setId(id); QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
updateNote(newNote); if (id >= 0 && url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
}
void NotesApi::updateNote(const Note &note) {
qDebug() << "Updating note: " << note.id();
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(note.id()));
if (note.isValid() && url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
qDebug() << "PUT" << url.toDisplayString(); qDebug() << "PUT" << url.toDisplayString();
m_authenticatedRequest.setUrl(url); m_authenticatedRequest.setUrl(url);
m_updateNoteReplies << m_manager.put(m_authenticatedRequest, noteApiData(note)); m_updateNoteReplies << m_manager.put(m_authenticatedRequest, QJsonDocument(note).toJson());
emit busyChanged(true); emit busyChanged(true);
} }
} }
@ -159,26 +124,6 @@ bool NotesApi::busy() const {
m_ocsReplies.empty()); m_ocsReplies.empty());
} }
const QByteArray NotesApi::noteApiData(const Note &note) {
QJsonObject json = note.toJsonObject();
json.remove(Note::noteFieldName(Note::Id));
if (note.modified() == 0)
json.remove(Note::noteFieldName(Note::Modified));
if (note.title().isNull())
json.remove(Note::noteFieldName(Note::Title));
if (note.category().isNull())
json.remove(Note::noteFieldName(Note::Category));
if (note.content().isNull())
json.remove(Note::noteFieldName(Note::Content));
json.remove(Note::noteFieldName(Note::Etag));
json.remove(Note::noteFieldName(Note::Error));
json.remove(Note::noteFieldName(Note::ErrorMessage));
return QJsonDocument(json).toJson(QJsonDocument::Compact);
}
// TODO ab hier überarbeiten
void NotesApi::setVerifySsl(bool verify) { void NotesApi::setVerifySsl(bool verify) {
if (verify != (m_request.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer)) { if (verify != (m_request.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer)) {
m_request.sslConfiguration().setPeerVerifyMode(verify ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone); m_request.sslConfiguration().setPeerVerifyMode(verify ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone);
@ -359,10 +304,11 @@ void NotesApi::verifyLogin(QString username, QString password) {
} }
} }
const QString NotesApi::errorMessage(ErrorCodes error) const { const QString NotesApi::errorMessage(int error) const {
QString message; QString message;
switch (error) { switch (error) {
case NoError: case NoError:
message = tr("No error");
break; break;
case NoConnectionError: case NoConnectionError:
message = tr("No network connection available"); message = tr("No network connection available");
@ -393,7 +339,7 @@ void NotesApi::requireAuthentication(QNetworkReply *reply, QAuthenticator *authe
authenticator->setPassword(password()); authenticator->setPassword(password());
} }
else else
emit error(AuthenticationError); emit noteError(AuthenticationError);
} }
void NotesApi::onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible) { void NotesApi::onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible) {
@ -405,13 +351,13 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
qDebug() << reply->error() << reply->errorString(); qDebug() << reply->error() << reply->errorString();
if (reply->error() == QNetworkReply::NoError) if (reply->error() == QNetworkReply::NoError)
emit error(NoError); emit noteError(NoError);
else if (reply->error() == QNetworkReply::AuthenticationRequiredError) else if (reply->error() == QNetworkReply::AuthenticationRequiredError)
emit error(AuthenticationError); emit noteError(AuthenticationError);
else if (reply->error() == QNetworkReply::ContentNotFoundError && m_pollReplies.contains(reply)) else if (reply->error() == QNetworkReply::ContentNotFoundError && m_pollReplies.contains(reply))
emit error(NoError); emit noteError(NoError);
else else
emit error(CommunicationError); emit noteError(CommunicationError);
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
QJsonDocument json = QJsonDocument::fromJson(data); QJsonDocument json = QJsonDocument::fromJson(data);
@ -435,9 +381,14 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
m_createNoteReplies.removeOne(reply); m_createNoteReplies.removeOne(reply);
} }
else if (m_updateNoteReplies.contains(reply)) { else if (m_updateNoteReplies.contains(reply)) {
qDebug() << "Update note reply"; qDebug() << "Update note reply";bool ok;
if (reply->error() == QNetworkReply::NoError && json.isObject()) QString idString = reply->url().path().split('/', QString::SkipEmptyParts).last();
updateApiNote(json.object()); int id = idString.toInt(&ok);
if (reply->error() == QNetworkReply::NoError && json.isObject() && id >= 0 && ok) {
QJsonObject obj = json.object();
obj["id"] = id;
updateApiNote(obj);
}
m_updateNoteReplies.removeOne(reply); m_updateNoteReplies.removeOne(reply);
} }
else if (m_deleteNoteReplies.contains(reply)) { else if (m_deleteNoteReplies.contains(reply)) {
@ -445,7 +396,7 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
bool ok; bool ok;
QString idString = reply->url().path().split('/', QString::SkipEmptyParts).last(); QString idString = reply->url().path().split('/', QString::SkipEmptyParts).last();
int id = idString.toInt(&ok); int id = idString.toInt(&ok);
if (reply->error() == QNetworkReply::NoError && ok) if (reply->error() == QNetworkReply::NoError && id >= 0 && ok)
emit noteDeleted(id); emit noteDeleted(id);
m_deleteNoteReplies.removeOne(reply); m_deleteNoteReplies.removeOne(reply);
} }
@ -510,7 +461,7 @@ void NotesApi::sslError(QNetworkReply *reply, const QList<QSslError> &errors) {
for (int i = 0; i < errors.size(); ++i) { for (int i = 0; i < errors.size(); ++i) {
qDebug() << errors[i].errorString(); qDebug() << errors[i].errorString();
} }
emit error(SslHandshakeError); emit noteError(SslHandshakeError);
} }
QUrl NotesApi::apiEndpointUrl(const QString endpoint) const { QUrl NotesApi::apiEndpointUrl(const QString endpoint) const {
@ -640,7 +591,7 @@ void NotesApi::updateApiNotes(const QJsonArray &json) {
} }
void NotesApi::updateApiNote(const QJsonObject &json) { void NotesApi::updateApiNote(const QJsonObject &json) {
Note note(json); int id = json["id"].toInt(-1);
if (!note.error()) if (id >= 0)
emit noteUpdated(note); emit noteUpdated(id, json);
} }

View file

@ -138,20 +138,20 @@ public:
AuthenticationError AuthenticationError
}; };
Q_ENUM(ErrorCodes) Q_ENUM(ErrorCodes)
Q_INVOKABLE const QString errorMessage(ErrorCodes error) const; Q_INVOKABLE const QString errorMessage(int error) const;
QString account() const { return m_account; } QString account() const { return m_account; }
void setAccount(const QString& account); void setAccount(const QString& account);
public slots: public slots:
Q_INVOKABLE void getAllNotes(const QStringList exclude = QStringList()); Q_INVOKABLE void getAllNotes(const QStringList& exclude = QStringList());
void getAllNotes(Note::NoteField exclude); //void getAllNotes(Note::NoteField exclude);
Q_INVOKABLE void getNote(const int id, const QStringList exclude = QStringList()); Q_INVOKABLE void getNote(const int id, const QStringList& exclude = QStringList());
void getNote(const int id, Note::NoteField exclude); //void getNote(const int id, Note::NoteField exclude);
Q_INVOKABLE void createNote(const QVariantMap& note); Q_INVOKABLE void createNote(const QJsonObject& note);
void createNote(const Note& note); //void createNote(const Note& note);
Q_INVOKABLE void updateNote(const int id, const QVariantMap& note); Q_INVOKABLE void updateNote(const int id, const QJsonObject& note);
void updateNote(const Note& note); //void updateNote(const Note& note);
Q_INVOKABLE void deleteNote(const int id); Q_INVOKABLE void deleteNote(const int id);
signals: signals:
@ -182,7 +182,6 @@ signals:
void loginStatusChanged(LoginStatus status); void loginStatusChanged(LoginStatus status);
void loginUrlChanged(QUrl url); void loginUrlChanged(QUrl url);
void error(ErrorCodes error);
public slots: public slots:
@ -196,7 +195,6 @@ private slots:
private: private:
QString m_account; QString m_account;
static const QByteArray noteApiData(const Note& note);
QUrl m_url; QUrl m_url;
QNetworkAccessManager m_manager; QNetworkAccessManager m_manager;

View file

@ -2,8 +2,7 @@
#define NOTESINTERFACE_H #define NOTESINTERFACE_H
#include <QObject> #include <QObject>
#include <QJsonObject>
#include "note.h"
class NotesInterface : public QObject class NotesInterface : public QObject
{ {
@ -18,22 +17,24 @@ public:
virtual QString account() const = 0; virtual QString account() const = 0;
virtual void setAccount(const QString& account) = 0; virtual void setAccount(const QString& account) = 0;
Q_INVOKABLE virtual const QString errorMessage(int error) const = 0;
public slots: public slots:
Q_INVOKABLE virtual void getAllNotes(const QStringList exclude = QStringList()) = 0; Q_INVOKABLE virtual void getAllNotes(const QStringList& exclude = QStringList()) = 0;
virtual void getAllNotes(Note::NoteField exclude) = 0; //virtual void getAllNotes(Note::NoteField exclude) = 0;
Q_INVOKABLE virtual void getNote(const int id, const QStringList exclude = QStringList()) = 0; Q_INVOKABLE virtual void getNote(const int id, const QStringList& exclude = QStringList()) = 0;
virtual void getNote(const int id, Note::NoteField) = 0; //virtual void getNote(const int id, Note::NoteField) = 0;
Q_INVOKABLE virtual void createNote(const QVariantMap& note) = 0; Q_INVOKABLE virtual void createNote(const QJsonObject& note) = 0;
virtual void createNote(const Note& note) = 0; //virtual void createNote(const Note& note) = 0;
Q_INVOKABLE virtual void updateNote(const int id, const QVariantMap& note) = 0; Q_INVOKABLE virtual void updateNote(const int id, const QJsonObject& note) = 0;
virtual void updateNote(const Note& note) = 0; //virtual void updateNote(const Note& note) = 0;
Q_INVOKABLE virtual void deleteNote(const int id) = 0; Q_INVOKABLE virtual void deleteNote(const int id) = 0;
signals: signals:
void accountChanged(const QString& account); void accountChanged(const QString& account);
void noteUpdated(const QVariantMap& note); void noteError(int error);
void noteUpdated(const Note& note); void noteUpdated(const int id, const QJsonObject& note);
//void noteUpdated(const Note& note);
void noteDeleted(const int id); void noteDeleted(const int id);
}; };

View file

@ -70,16 +70,17 @@ QList<int> NotesModel::ids() const {
return ids; return ids;
} }
int NotesModel::insertNote(const Note &note) { int NotesModel::insertNote(const int id, const QJsonObject& note) {
qDebug() << "Inserting note: " << note.id(); qDebug() << "Inserting note: " << id;
int position = m_notes.indexOf(note); Note tmpNote(note);
int position = m_notes.indexOf(tmpNote);
if (position >= 0) { if (position >= 0) {
if (m_notes.at(position).equal(note)) { if (m_notes.at(position).equal(tmpNote)) {
qDebug() << "Note already present and unchanged."; qDebug() << "Note already present and unchanged.";
} }
else { else {
qDebug() << "Note already present, updating it."; qDebug() << "Note already present, updating it.";
m_notes.replace(position, note); m_notes.replace(position, tmpNote);
emit dataChanged(index(position), index(position)); emit dataChanged(index(position), index(position));
} }
} }
@ -87,16 +88,17 @@ int NotesModel::insertNote(const Note &note) {
qDebug() << "New note, adding it"; qDebug() << "New note, adding it";
position = m_notes.size(); position = m_notes.size();
beginInsertRows(QModelIndex(), position, position); beginInsertRows(QModelIndex(), position, position);
m_notes.append(note); m_notes.append(tmpNote);
endInsertRows(); endInsertRows();
emit dataChanged(index(position), index(position)); emit dataChanged(index(position), index(position));
} }
return position; return position;
} }
bool NotesModel::removeNote(const Note &note) { bool NotesModel::removeNote(const QJsonObject &note) {
qDebug() << "Removing note: " << note.id(); Note tmpNote(note);
int position = m_notes.indexOf(note); qDebug() << "Removing note: " << tmpNote.id();
int position = m_notes.indexOf(tmpNote);
if (position >= 0 && position < m_notes.size()) { if (position >= 0 && position < m_notes.size()) {
beginRemoveRows(QModelIndex(), position, position); beginRemoveRows(QModelIndex(), position, position);
m_notes.removeAt(position); m_notes.removeAt(position);
@ -108,8 +110,7 @@ bool NotesModel::removeNote(const Note &note) {
} }
bool NotesModel::removeNote(int id) { bool NotesModel::removeNote(int id) {
qDebug() << "Removing note: " << id; return removeNote(QJsonObject{ {"id", id} } );
return removeNote(Note(QJsonObject{ {"id", id} } ));
} }
void NotesModel::clear() { void NotesModel::clear() {

View file

@ -66,8 +66,8 @@ public:
virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles); virtual bool setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles);
public slots: public slots:
int insertNote(const Note &note); int insertNote(const int id, const QJsonObject& note);
bool removeNote(const Note &note); bool removeNote(const QJsonObject& note);
bool removeNote(int id); bool removeNote(int id);
Q_INVOKABLE void clear(); Q_INVOKABLE void clear();
Q_INVOKABLE QList<int> ids() const; Q_INVOKABLE QList<int> ids() const;

View file

@ -1,5 +1,7 @@
#include "notesstore.h" #include "notesstore.h"
#include <QJsonDocument>
#include <QDateTime>
#include <QDebug> #include <QDebug>
const QString NotesStore::m_suffix = "json"; const QString NotesStore::m_suffix = "json";
@ -26,25 +28,57 @@ void NotesStore::setAccount(const QString& account) {
qDebug() << "Setting account: " << account; qDebug() << "Setting account: " << account;
if (account != m_dir.path()) { if (account != m_dir.path()) {
if (m_dir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) { if (m_dir != QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation))) {
m_dir.cdUp(); m_dir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
} }
if (!account.isEmpty()) { if (!account.isEmpty()) {
m_dir.setPath(account); m_dir.setPath(account);
if (!m_dir.mkpath(".")) { if (m_dir.mkpath(".")) {
emit accountChanged(m_dir.path());
}
else {
qDebug() << "Failed to create or already present: " << m_dir.path(); 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(); //qDebug() << account << m_dir.path();
emit accountChanged(m_dir.path());
} }
} }
void NotesStore::getAllNotes(const QStringList exclude) { const QString NotesStore::errorMessage(int error) const {
getAllNotes(Note::noteFieldsFromStringList(exclude)); 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;
} }
void NotesStore::getAllNotes(Note::NoteField exclude) { void NotesStore::getAllNotes(const QStringList& exclude) {
qDebug() << "Getting all notes"; qDebug() << "Getting all notes";
if (m_dir.exists()) {
QFileInfoList files = m_dir.entryInfoList(); QFileInfoList files = m_dir.entryInfoList();
for (int i = 0; i < files.size(); ++i) { for (int i = 0; i < files.size(); ++i) {
bool ok; bool ok;
@ -54,33 +88,33 @@ void NotesStore::getAllNotes(Note::NoteField exclude) {
} }
} }
} }
else {
void NotesStore::getNote(const int id, const QStringList exclude) { emit noteError(DirCannotReadError);
getNote(id, Note::noteFieldsFromStringList(exclude)); }
} }
void NotesStore::getNote(const int id, Note::NoteField exclude) { void NotesStore::getNote(const int id, const QStringList& exclude) {
qDebug() << "Getting note: " << id; qDebug() << "Getting note: " << id;
if (id >= 0) { if (id >= 0) {
Note note = readNoteFile(id, exclude); QJsonObject note = readNoteFile(id, exclude);
if (note.isValid()) if (note.value("id").toInt(-1) >= 0)
emit noteUpdated(note); emit noteUpdated(id, note);
}
else {
qDebug() << "Skipping, invalid ID";
} }
} }
void NotesStore::createNote(const QVariantMap &note) { void NotesStore::createNote(const QJsonObject& note) {
createNote(Note(QJsonObject::fromVariantMap(note))); int id = note.value("id").toInt(-1);
} qDebug() << "Creating note: " << id;
if (id < 0) {
void NotesStore::createNote(const Note& note) {
qDebug() << "Creating note: " << note.id();
if (!note.isValid()) {
// TODO probably crate files with an '.json.<NUMBER>.new' extension // TODO probably crate files with an '.json.<NUMBER>.new' extension
qDebug() << "Creating notes without the server API is not supported yet!"; qDebug() << "Creating notes without the server API is not supported yet!";
} }
else if (!noteFileExists(note.id())) { else if (!noteFileExists(id)) {
if (writeNoteFile(note)) { if (writeNoteFile(id, note)) {
emit noteUpdated(note); emit noteUpdated(id, note);
} }
} }
else { else {
@ -88,21 +122,33 @@ void NotesStore::createNote(const Note& note) {
} }
} }
void NotesStore::updateNote(const int id, const QVariantMap &note) { void NotesStore::updateNote(const int id, const QJsonObject& note) {
Note newNote(QJsonObject::fromVariantMap(note)); qDebug() << "Updating note: " << id;
newNote.setId(id); if (id >= 0) {
updateNote(newNote); 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) {
void NotesStore::updateNote(const Note& note) { tmpNote["modified"] = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000;
qDebug() << "Updating note: " << note.id(); }
if (note.isValid()) { if (writeNoteFile(id, tmpNote)) {
Note file = readNoteFile(note.id()); emit noteUpdated(id, tmpNote);
if (!file.equal(note) && note > file) {
if (writeNoteFile(note)) {
emit noteUpdated(note);
} }
} }
else {
qDebug() << "Skipping, note is older" << note.value("modified") << tmpNote.value("modified");
}
}
else {
qDebug() << "Skipping, note is equal";
}
}
else {
qDebug() << "Skipping, invalid ID";
} }
} }
@ -118,7 +164,7 @@ bool NotesStore::noteFileExists(const int id) const {
return fileinfo.exists(); return fileinfo.exists();
} }
Note NotesStore::readNoteFile(const int id, Note::NoteField exclude) const { QJsonObject NotesStore::readNoteFile(const int id, const QStringList& exclude) {
QJsonObject json; QJsonObject json;
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix)); QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
QFile file(fileinfo.filePath()); QFile file(fileinfo.filePath());
@ -127,36 +173,40 @@ Note NotesStore::readNoteFile(const int id, Note::NoteField exclude) const {
QByteArray data = file.readAll(); QByteArray data = file.readAll();
json = QJsonDocument::fromJson(data).object(); json = QJsonDocument::fromJson(data).object();
file.close(); file.close();
QList<Note::NoteField> noteFields = Note::noteFields(); for (int i = 0; i < exclude.size(); ++i) {
QFlags<Note::NoteField> flags(exclude); json.remove(exclude[i]);
for (int i = 0; i < noteFields.size(); ++i) {
if (flags.testFlag(noteFields[i])) {
json.remove(Note::noteFieldName(noteFields[i]));
} }
} }
else {
emit noteError(FileCannotReadError);
} }
} }
else {
//emit noteError(FileNotFoundError);
}
return json; return json;
} }
bool NotesStore::writeNoteFile(const Note &note) const { bool NotesStore::writeNoteFile(const int id, const QJsonObject& note) {
bool success = false; bool success = false;
if (!account().isEmpty()) { if (!account().isEmpty()) {
QJsonDocument json = note.toJsonDocument(); QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(note.id()).arg(m_suffix));
QFile file(fileinfo.filePath()); QFile file(fileinfo.filePath());
if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) {
QByteArray data = json.toJson(); QByteArray data = QJsonDocument(note).toJson();
if (file.write(data) == data.size()) { if (file.write(data) == data.size()) {
success = true; success = true;
} }
file.close(); file.close();
} }
else {
emit noteError(FileCannotWriteError);
}
} }
return success; return success;
} }
bool NotesStore::removeNoteFile(const int id) const { bool NotesStore::removeNoteFile(const int id) {
bool success = false; bool success = false;
if (!account().isEmpty()) { if (!account().isEmpty()) {
QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix)); QFileInfo fileinfo(m_dir, QString("%1.%2").arg(id).arg(m_suffix));
@ -165,6 +215,12 @@ bool NotesStore::removeNoteFile(const int id) const {
if (file.remove()) { if (file.remove()) {
success = true; success = true;
} }
else {
emit noteError(FileCannotWriteError);
}
}
else {
emit noteError(FileNotFoundError);
} }
} }
return success; return success;

View file

@ -20,15 +20,27 @@ public:
QString account() const; QString account() const;
void setAccount(const QString& account); void setAccount(const QString& account);
enum ErrorCodes {
NoError,
FileNotFoundError,
FileCannotReadError,
FileCannotWriteError,
DirNotFoundError,
DirCannotReadError,
DirCannotWriteError
};
Q_ENUM(ErrorCodes)
Q_INVOKABLE const QString errorMessage(int error) const;
public slots: public slots:
Q_INVOKABLE void getAllNotes(const QStringList exclude = QStringList()); Q_INVOKABLE void getAllNotes(const QStringList& exclude = QStringList());
void getAllNotes(Note::NoteField exclude); //void getAllNotes(Note::NoteField exclude);
Q_INVOKABLE void getNote(const int id, const QStringList exclude = QStringList()); Q_INVOKABLE void getNote(const int id, const QStringList& exclude = QStringList());
void getNote(const int id, Note::NoteField exclude); //void getNote(const int id, Note::NoteField exclude);
Q_INVOKABLE void createNote(const QVariantMap& note); Q_INVOKABLE void createNote(const QJsonObject& note);
void createNote(const Note& note); //void createNote(const Note& note);
Q_INVOKABLE void updateNote(const int id, const QVariantMap& note); Q_INVOKABLE void updateNote(const int id, const QJsonObject& note);
void updateNote(const Note& note); //void updateNote(const Note& note);
Q_INVOKABLE void deleteNote(const int id); Q_INVOKABLE void deleteNote(const int id);
private: private:
@ -36,9 +48,9 @@ private:
const static QString m_suffix; const static QString m_suffix;
bool noteFileExists(const int id) const; bool noteFileExists(const int id) const;
Note readNoteFile(const int id, Note::NoteField exclude = Note::None) const; QJsonObject readNoteFile(const int id, const QStringList& exclude = QStringList());
bool writeNoteFile(const Note& note) const; bool writeNoteFile(const int id, const QJsonObject& note);
bool removeNoteFile(const int id) const; bool removeNoteFile(const int id);
}; };
#endif // NOTESSTORE_H #endif // NOTESSTORE_H

View file

@ -274,6 +274,10 @@
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unbekannter Fehler</translation> <translation>Unbekannter Fehler</translation>
</message> </message>
<message>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>NotesPage</name> <name>NotesPage</name>
@ -354,6 +358,41 @@
<translation>Ein Fehler ist aufgetreten</translation> <translation>Ein Fehler ist aufgetreten</translation>
</message> </message>
</context> </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> <context>
<name>SettingsPage</name> <name>SettingsPage</name>
<message> <message>
@ -686,8 +725,12 @@ You can also use other markdown syntax inside them.</source>
<translation>Synchronisiert</translation> <translation>Synchronisiert</translation>
</message> </message>
<message> <message>
<source>Error</source> <source>API error</source>
<translation>Fehler</translation> <translation type="unfinished"></translation>
</message>
<message>
<source>File error</source>
<translation type="unfinished"></translation>
</message> </message>
</context> </context>
</TS> </TS>

View file

@ -274,6 +274,10 @@
<source>Unknown error</source> <source>Unknown error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>NotesPage</name> <name>NotesPage</name>
@ -354,6 +358,41 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </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> <context>
<name>SettingsPage</name> <name>SettingsPage</name>
<message> <message>
@ -686,7 +725,11 @@ You can also use other markdown syntax inside them.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Error</source> <source>API error</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>File error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>

View file

@ -245,12 +245,12 @@
<context> <context>
<name>Note</name> <name>Note</name>
<message> <message>
<location filename="../src/note.cpp" line="335"/> <location filename="../src/note.cpp" line="357"/>
<source>Today</source> <source>Today</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/note.cpp" line="337"/> <location filename="../src/note.cpp" line="359"/>
<source>Yesterday</source> <source>Yesterday</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -311,27 +311,32 @@
<context> <context>
<name>NotesApi</name> <name>NotesApi</name>
<message> <message>
<location filename="../src/notesapi.cpp" line="368"/> <location filename="../src/notesapi.cpp" line="311"/>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesapi.cpp" line="314"/>
<source>No network connection available</source> <source>No network connection available</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="371"/> <location filename="../src/notesapi.cpp" line="317"/>
<source>Failed to communicate with the Nextcloud server</source> <source>Failed to communicate with the Nextcloud server</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="374"/> <location filename="../src/notesapi.cpp" line="320"/>
<source>An error occured while establishing an encrypted connection</source> <source>An error occured while establishing an encrypted connection</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="377"/> <location filename="../src/notesapi.cpp" line="323"/>
<source>Could not authenticate to the Nextcloud instance</source> <source>Could not authenticate to the Nextcloud instance</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../src/notesapi.cpp" line="380"/> <location filename="../src/notesapi.cpp" line="326"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -374,66 +379,109 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="188"/> <location filename="../qml/pages/NotesPage.qml" line="189"/>
<source>Modified</source> <source>Modified</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="191"/> <location filename="../qml/pages/NotesPage.qml" line="192"/>
<source>Delete</source> <source>Delete</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="193"/> <location filename="../qml/pages/NotesPage.qml" line="194"/>
<source>Deleting note</source> <source>Deleting note</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="224"/> <location filename="../qml/pages/NotesPage.qml" line="225"/>
<source>Loading notes...</source> <source>Loading notes...</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="230"/> <location filename="../qml/pages/NotesPage.qml" line="231"/>
<source>No account yet</source> <source>No account yet</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="231"/> <location filename="../qml/pages/NotesPage.qml" line="232"/>
<source>Got to the settings to add an account</source> <source>Got to the settings to add an account</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="237"/> <location filename="../qml/pages/NotesPage.qml" line="238"/>
<source>No notes yet</source> <source>No notes yet</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="238"/> <location filename="../qml/pages/NotesPage.qml" line="239"/>
<source>Pull down to add a note</source> <source>Pull down to add a note</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="244"/> <location filename="../qml/pages/NotesPage.qml" line="245"/>
<source>No result</source> <source>No result</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="245"/> <location filename="../qml/pages/NotesPage.qml" line="246"/>
<source>Try another query</source> <source>Try another query</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="251"/> <location filename="../qml/pages/NotesPage.qml" line="252"/>
<source>An error occurred</source> <source>An error occurred</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../qml/pages/NotesPage.qml" line="262"/> <location filename="../qml/pages/NotesPage.qml" line="263"/>
<source>Open the settings to configure your Nextcloud accounts</source> <source>Open the settings to configure your Nextcloud accounts</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>
<context>
<name>NotesStore</name>
<message>
<location filename="../src/notesstore.cpp" line="52"/>
<source>No error</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="55"/>
<source>File not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="58"/>
<source>Cannot read from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="61"/>
<source>Cannot write to the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="64"/>
<source>Directory not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="67"/>
<source>Cannot read from directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="70"/>
<source>Cannot create or write to directory</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/notesstore.cpp" line="73"/>
<source>Unknown error</source>
<translation type="unfinished"></translation>
</message>
</context>
<context> <context>
<name>SettingsPage</name> <name>SettingsPage</name>
<message> <message>
@ -844,9 +892,14 @@ You can also use other markdown syntax inside them.</source>
<source>Synced</source> <source>Synced</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="141"/>
<source>API error</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../qml/harbour-nextcloudnotes.qml" line="134"/> <location filename="../qml/harbour-nextcloudnotes.qml" line="134"/>
<source>Error</source> <source>File error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
</context> </context>