ubuntu-touch-coreapps-reviewers team mailing list archive
-
ubuntu-touch-coreapps-reviewers team
-
Mailing list archive
-
Message #00734
[Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
Michael Zanetti has proposed merging lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app.
Commit message:
implement proper conflict handling
Requested reviews:
Ubuntu Reminders app developers (reminders-app-dev)
For more details, see:
https://code.launchpad.net/~mzanetti/reminders-app/conflict-handling/+merge/253002
For testing, this is how to produce the situation:
* create a note, make sure evernote and the app are synced
* put the app to offline mode (for example using flight mode)
* Do a change on the phone and in the website
* reconnect the app
=> the note should mark the note as conflicting and upon opening it, it should present you with the conflict resolving page.
Also handles the cases if a note has been changed in one place and deleted on another.
--
Your team Ubuntu Reminders app developers is requested to review the proposed merge of lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app.
=== added file 'src/app/qml/components/ConflictDelegate.qml'
--- src/app/qml/components/ConflictDelegate.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/components/ConflictDelegate.qml 2015-03-15 20:04:06 +0000
@@ -0,0 +1,72 @@
+/*
+ * Copyright: 2015 Canonical, Ltd
+ *
+ * This file is part of reminders
+ *
+ * reminders is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * reminders is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.2
+import QtQuick.Layouts 1.1
+import Ubuntu.Components 1.1
+import Evernote 0.1
+
+ColumnLayout {
+ id: root
+ property var note: null
+
+ signal clicked();
+ signal deleteThis();
+ signal keepThis();
+
+ RowLayout {
+ Label {
+ text: i18n.tr("Notebook:")
+ }
+ Label {
+ text: root.note ? NotesStore.notebook(root.note.notebookGuid).name : ""
+ }
+ }
+
+ NotesDelegate {
+ Layout.fillWidth: true
+
+ title: root.note.title
+ content: root.note.plaintextContent
+
+ resource: root.note.resourceUrls.length > 0 ? root.note.resourceUrls[0] : ""
+ notebookColor: preferences.colorForNotebook(root.note.notebookGuid)
+ reminder: root.note.reminder
+ date: root.note.updated
+ conflicting: true
+
+ onItemClicked: root.clicked();
+ onDeleteNote: root.deleteThis();
+ onKeepThis: root.keepThis();
+ deleted: root.note.deleted
+ }
+ RowLayout {
+ visible: root.note && root.note.reminder
+ Label {
+ text: i18n.tr("Reminder:")
+ }
+ Label {
+ text: root.note ? root.note.reminderTimeString : ""
+ Layout.fillWidth: true
+ }
+ CheckBox {
+ enabled: false
+ checked: root.note ? root.note.reminderDone : false
+ }
+ }
+}
=== modified file 'src/app/qml/components/ListItemWithActions.qml'
--- src/app/qml/components/ListItemWithActions.qml 2015-02-13 01:02:58 +0000
+++ src/app/qml/components/ListItemWithActions.qml 2015-03-15 20:04:06 +0000
@@ -29,6 +29,7 @@
property bool triggerActionOnMouseRelease: false
property color color: Theme.palette.normal.background
property color selectedColor: "#E6E6E6"
+ property color selectedRightActionColor: UbuntuColors.lightAubergine
property bool selected: false
property bool selectionMode: false
property alias internalAnchors: mainContents.anchors
@@ -246,7 +247,7 @@
height: units.gu(3)
name: modelData.iconName
source: modelData.iconSource
- color: root.activeAction === modelData ? UbuntuColors.lightAubergine : UbuntuColors.lightGrey
+ color: root.activeAction === modelData ? root.selectedRightActionColor : UbuntuColors.lightGrey
}
}
}
=== modified file 'src/app/qml/components/NotebooksDelegate.qml'
--- src/app/qml/components/NotebooksDelegate.qml 2015-03-04 00:23:45 +0000
+++ src/app/qml/components/NotebooksDelegate.qml 2015-03-15 20:04:06 +0000
@@ -39,6 +39,8 @@
}
}
+ selectedRightActionColor: UbuntuColors.green
+ triggerActionOnMouseRelease: true
rightSideActions: [
Action {
iconName: model.isDefaultNotebook ? "starred" : "non-starred"
=== modified file 'src/app/qml/components/NotesDelegate.qml'
--- src/app/qml/components/NotesDelegate.qml 2015-02-23 18:44:26 +0000
+++ src/app/qml/components/NotesDelegate.qml 2015-03-15 20:04:06 +0000
@@ -37,6 +37,7 @@
property bool synced
property bool syncError
property bool conflicting
+ property bool deleted
property string notebookColor
signal deleteNote()
@@ -44,6 +45,9 @@
signal editReminder()
signal editTags()
+ // For conflict handling
+ signal keepThis();
+
leftSideAction: Action {
iconName: "delete"
text: i18n.tr("Delete")
@@ -52,7 +56,18 @@
}
}
- rightSideActions: [
+ selectedRightActionColor: UbuntuColors.green
+ triggerActionOnMouseRelease: true
+ rightSideActions: root.conflicting ? conflictActions : enabledRightSideActions
+ property list<Action> conflictActions: [
+ Action {
+ iconName: "tick"
+ onTriggered: {
+ root.keepThis();
+ }
+ }
+ ]
+ property list<Action> enabledRightSideActions: [
Action {
iconName: "alarm-clock"
text: i18n.tr("Reminder")
@@ -114,6 +129,7 @@
font.weight: Font.Light
elide: Text.ElideRight
color: root.notebookColor
+ font.strikeout: root.deleted
}
Label {
Layout.fillWidth: true
@@ -126,6 +142,7 @@
maximumLineCount: 2
fontSize: "small"
color: "black"
+ font.strikeout: root.deleted
}
Label {
=== modified file 'src/app/qml/components/RemindersDelegate.qml'
--- src/app/qml/components/RemindersDelegate.qml 2015-02-13 00:47:37 +0000
+++ src/app/qml/components/RemindersDelegate.qml 2015-03-15 20:04:06 +0000
@@ -41,6 +41,8 @@
}
}
+ selectedRightActionColor: UbuntuColors.green
+ triggerActionOnMouseRelease: true
rightSideActions: [
Action {
iconSource: root.note.reminderDone ? "image://theme/select" : "../images/unchecked.svg"
=== added file 'src/app/qml/components/ResolveConflictConfirmationDialog.qml'
--- src/app/qml/components/ResolveConflictConfirmationDialog.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/components/ResolveConflictConfirmationDialog.qml 2015-03-15 20:04:06 +0000
@@ -0,0 +1,73 @@
+/*
+ * Copyright: 2015 Canonical, Ltd
+ *
+ * This file is part of reminders
+ *
+ * reminders is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * reminders is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.2
+import Ubuntu.Components 1.1
+import Ubuntu.Components.Popups 1.0
+
+Dialog {
+ id: root
+ title: i18n.tr("Resolve conflict")
+ text: {
+ var fullText;
+ if (keepLocal) {
+ if (remoteDeleted) {
+ fullText = i18n.tr("This will <b>keep the changes made on this device</b> and <b>restore the note on Evernote</b>.");
+ } else if (localDeleted) {
+ fullText = i18n.tr("This will <b>delete the changed note on Evernote</b>.");
+ } else {
+ fullText = i18n.tr("This will <b>keep the changes made on this device</b> and <b>discard any changes made on Evernote</b>.");
+ }
+ } else {
+ if (remoteDeleted) {
+ fullText = i18n.tr("This will <b>delete the changed note on this device</b>.");
+ } else if (localDeleted) {
+ fullText = i18n.tr("This will <b>download the changed note from Evernote</b> and <b>restore it on this device</b>.");
+ } else {
+ fullText = i18n.tr("This will <b>download the changed note from Evernote</b> and <b>discard any changes made on this device</b>.");
+ }
+ }
+ fullText += "<br><br>" + i18n.tr("Are you sure you want to continue?");
+ return fullText;
+ }
+
+ property bool keepLocal: true
+ property bool remoteDeleted: false
+ property bool localDeleted: false
+
+ signal accepted();
+ signal rejected();
+
+ Button {
+ text: i18n.tr("Yes")
+ color: UbuntuColors.green
+ onClicked: {
+ root.accepted();
+ PopupUtils.close(root);
+ }
+ }
+
+ Button {
+ text: i18n.tr("No")
+ color: UbuntuColors.red
+ onClicked: {
+ root.rejected();
+ PopupUtils.close(root)
+ }
+ }
+}
=== modified file 'src/app/qml/components/StatusBar.qml'
--- src/app/qml/components/StatusBar.qml 2015-03-04 00:23:45 +0000
+++ src/app/qml/components/StatusBar.qml 2015-03-15 20:04:06 +0000
@@ -38,6 +38,7 @@
id: label
width: parent.width - x
wrapMode: Text.WordWrap
+ anchors.verticalCenter: parent.verticalCenter
}
}
}
=== modified file 'src/app/qml/components/TagsDelegate.qml'
--- src/app/qml/components/TagsDelegate.qml 2015-03-04 00:23:45 +0000
+++ src/app/qml/components/TagsDelegate.qml 2015-03-15 20:04:06 +0000
@@ -37,6 +37,8 @@
}
}
+ selectedRightActionColor: UbuntuColors.green
+ triggerActionOnMouseRelease: true
rightSideActions: [
Action {
iconName: "edit"
=== modified file 'src/app/qml/reminders.qml'
--- src/app/qml/reminders.qml 2015-03-08 18:47:51 +0000
+++ src/app/qml/reminders.qml 2015-03-15 20:04:06 +0000
@@ -109,20 +109,41 @@
pagestack.push(accountPage);
}
- function displayNote(note) {
+ function displayNote(note, conflictMode) {
print("displayNote:", note.guid)
note.load(true);
if (root.narrowMode) {
print("creating noteview");
- var component = Qt.createComponent(Qt.resolvedUrl("ui/NotePage.qml"));
- var page = component.createObject(root);
+ var page;
+ if (!conflictMode && note.conflicting) {
+ // We want to open the note normally even though it is conflicting! Show the Conflict page instead.
+ var component = Qt.createComponent(Qt.resolvedUrl("ui/NoteConflictPage.qml"));
+ page = component.createObject(root, {note: note});
+ page.displayNote.connect(function(note) { root.displayNote(note, true); } );
+ page.resolveConflict.connect(function(keepLocal) {
+ var confirmation = PopupUtils.open(Qt.resolvedUrl("components/ResolveConflictConfirmationDialog.qml"), page, {keepLocal: keepLocal, remoteDeleted: note.conflictingNote.deleted, localDeleted: note.deleted});
+ confirmation.accepted.connect(function() {
+ print("dialog accepted. keepLocal:", keepLocal)
+ NotesStore.resolveConflict(note.guid, keepLocal ? NotesStore.KeepLocal : NotesStore.KeepRemote);
+ pagestack.pop();
+ });
+ })
+ } else {
+ var component = Qt.createComponent(Qt.resolvedUrl("ui/NotePage.qml"));
+ page = component.createObject(root, {readOnly: conflictMode });
+ page.editNote.connect(function(note) {root.switchToEditMode(note)})
+ page.openTaggedNotes.connect(function(title, tagGuid) {pagestack.pop();root.openTaggedNotes(title, tagGuid, true)})
+ }
page.note = note;
- page.editNote.connect(function(note) {root.switchToEditMode(note)})
- page.openTaggedNotes.connect(function(title, tagGuid) {pagestack.pop();root.openTaggedNotes(title, tagGuid, true)})
pagestack.push(page)
} else {
- var view = sideViewLoader.embed(Qt.resolvedUrl("ui/NoteView.qml"))
- view.openTaggedNotes.connect(function(title, tagGuid) {root.openTaggedNotes(title, tagGuid, false)})
+ var view;
+ if (note.conflicting) {
+ view = sideViewLoader.embed(Qt.resolvedUrl("ui/NoteConflictView.qml"))
+ } else {
+ view = sideViewLoader.embed(Qt.resolvedUrl("ui/NoteView.qml"))
+ view.openTaggedNotes.connect(function(title, tagGuid) {root.openTaggedNotes(title, tagGuid, false)})
+ }
view.note = note;
}
}
=== added file 'src/app/qml/ui/NoteConflictPage.qml'
--- src/app/qml/ui/NoteConflictPage.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/ui/NoteConflictPage.qml 2015-03-15 20:04:06 +0000
@@ -0,0 +1,40 @@
+/*
+ * Copyright: 2015 Canonical, Ltd
+ *
+ * This file is part of reminders
+ *
+ * reminders is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * reminders is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import Evernote 0.1
+import "../components"
+
+Page {
+ id: root
+ property alias note: conflictView.note
+ title: i18n.tr("Conflicting changes")
+
+ signal displayNote(var note)
+
+ signal resolveConflict(bool keepLocal);
+
+ NoteConflictView {
+ id: conflictView
+ anchors.fill: parent
+
+ onDisplayNote: root.displayNote(note)
+ onResolveConflict: root.resolveConflict(keepLocal)
+ }
+}
=== added file 'src/app/qml/ui/NoteConflictView.qml'
--- src/app/qml/ui/NoteConflictView.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/ui/NoteConflictView.qml 2015-03-15 20:04:06 +0000
@@ -0,0 +1,104 @@
+/*
+ * Copyright: 2015 Canonical, Ltd
+ *
+ * This file is part of reminders
+ *
+ * reminders is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * reminders is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+import QtQuick 2.3
+import Ubuntu.Components 1.1
+import Ubuntu.Components.ListItems 1.0
+import QtQuick.Layouts 1.1
+import Evernote 0.1
+import "../components"
+
+Item {
+ id: root
+
+ property var note: null
+ readonly property var conflictingNote: note.conflictingNote
+
+ signal displayNote(var note);
+ signal resolveConflict(bool keepLocal);
+
+ Flickable {
+ anchors.fill: parent
+ contentHeight: column.height + units.gu(2)
+ clip: true
+
+ ColumnLayout {
+ id: column
+ anchors { left: parent.left; right: parent.right; top: parent.top}
+ anchors.margins: units.gu(1)
+ spacing: units.gu(2)
+
+ RowLayout {
+ spacing: units.gu(2)
+ Icon {
+ height: units.gu(8)
+ width: height
+ name: "weather-severe-alert-symbolic"
+ }
+ Label {
+ text: i18n.tr("This note has been modified in multiple places. Please choose the version to be opened and delete the other.")
+ Layout.fillWidth: true
+ wrapMode: Text.WordWrap
+ }
+ }
+
+ ThinDivider {}
+
+ Label {
+ text: root.note && root.note.deleted ? i18n.tr("Deleted on this device:") : i18n.tr("Modified on this device:")
+ Layout.fillWidth: true
+ font.bold: true
+ wrapMode: Text.WordWrap
+ }
+
+ ConflictDelegate {
+ Layout.fillWidth: true
+ note: root.note
+
+ onClicked: {
+ if (root.note) {
+ root.displayNote(note)
+ }
+ }
+ onDeleteThis: root.resolveConflict(false)
+ onKeepThis: root.resolveConflict(true)
+ }
+
+ ThinDivider {}
+
+ Label {
+ text: root.note && root.conflictingNote && root.note.conflictingNote.deleted ? i18n.tr("Deleted from Evernote:") : i18n.tr("Modified somewhere else:")
+ Layout.fillWidth: true
+ font.bold: true
+ wrapMode: Text.WordWrap
+ }
+
+ ConflictDelegate {
+ Layout.fillWidth: true
+ note: root.note ? root.note.conflictingNote : null
+ onClicked: {
+ if (root.note && root.note.conflictingNote) {
+ root.displayNote(root.note.conflictingNote);
+ }
+ }
+ onDeleteThis: root.resolveConflict(true)
+ onKeepThis: root.resolveConflict(false)
+ }
+ }
+ }
+}
=== modified file 'src/app/qml/ui/NotePage.qml'
--- src/app/qml/ui/NotePage.qml 2015-02-16 22:05:34 +0000
+++ src/app/qml/ui/NotePage.qml 2015-03-15 20:04:06 +0000
@@ -25,31 +25,29 @@
id: root
title: noteView.title
property alias note: noteView.note
+ property bool readOnly: false
signal editNote(var note)
signal openTaggedNotes(string title, string tagGuid)
- tools: ToolbarItems {
- ToolbarButton {
- action: Action {
+ head {
+ actions: !root.readOnly ? normalActions : []
+ property list<Action> normalActions: [
+ Action {
text: i18n.tr("Edit")
iconName: "edit"
onTriggered: {
root.editNote(root.note)
}
- }
- }
- ToolbarButton {
- action: Action {
+ },
+ Action {
text: note.reminder ? i18n.tr("Edit reminder") : i18n.tr("Set reminder")
iconName: note.reminder ? "reminder" : "reminder-new"
onTriggered: {
pageStack.push(Qt.resolvedUrl("SetReminderPage.qml"), { note: root.note});
}
- }
- }
- ToolbarButton {
- action: Action {
+ },
+ Action {
text: i18n.tr("Delete")
iconName: "delete"
onTriggered: {
@@ -57,8 +55,8 @@
pagestack.pop();
}
}
- }
- }
+ ]
+ }
NoteView {
id: noteView
=== modified file 'src/app/qml/ui/NotebooksPage.qml'
--- src/app/qml/ui/NotebooksPage.qml 2015-03-04 00:23:45 +0000
+++ src/app/qml/ui/NotebooksPage.qml 2015-03-15 20:04:06 +0000
@@ -119,7 +119,6 @@
delegate: NotebooksDelegate {
width: parent.width
height: units.gu(10)
- triggerActionOnMouseRelease: true
onItemClicked: {
print("selected notebook:", model.guid)
=== modified file 'src/app/qml/ui/NotesPage.qml'
--- src/app/qml/ui/NotesPage.qml 2015-03-08 18:47:51 +0000
+++ src/app/qml/ui/NotesPage.qml 2015-03-15 20:04:06 +0000
@@ -161,7 +161,6 @@
model.updated : model.created
content: model.tagline
- triggerActionOnMouseRelease: true
tags: {
var tags = new Array();
for (var i = 0; i < model.tagGuids.length; i++) {
@@ -176,15 +175,15 @@
loading: model.loading
syncError: model.syncError
conflicting: model.conflicting
+ locked: conflicting
+ deleted: model.deleted
Component.onCompleted: {
notes.note(model.guid).load(false);
}
onItemClicked: {
- if (!model.conflicting) {
- root.selectedNote = NotesStore.note(guid);
- }
+ root.selectedNote = NotesStore.note(guid);
}
onDeleteNote: {
=== modified file 'src/app/qml/ui/RemindersPage.qml'
--- src/app/qml/ui/RemindersPage.qml 2015-03-01 22:32:41 +0000
+++ src/app/qml/ui/RemindersPage.qml 2015-03-15 20:04:06 +0000
@@ -59,7 +59,6 @@
delegate: RemindersDelegate {
width: remindersListView.width
note: notes.note(guid)
- triggerActionOnMouseRelease: true
onItemClicked: {
root.selectedNote = NotesStore.note(guid);
=== modified file 'src/app/qml/ui/SearchNotesPage.qml'
--- src/app/qml/ui/SearchNotesPage.qml 2015-03-04 23:24:54 +0000
+++ src/app/qml/ui/SearchNotesPage.qml 2015-03-15 20:04:06 +0000
@@ -90,8 +90,8 @@
loading: model.loading
syncError: model.syncError
conflicting: model.conflicting
+ deleted: model.deleted
- triggerActionOnMouseRelease: true
tags: {
var tags = new Array();
for (var i = 0; i < model.tagGuids.length; i++) {
=== modified file 'src/app/qml/ui/TagsPage.qml'
--- src/app/qml/ui/TagsPage.qml 2015-03-04 19:48:51 +0000
+++ src/app/qml/ui/TagsPage.qml 2015-03-15 20:04:06 +0000
@@ -83,7 +83,6 @@
delegate: TagsDelegate {
width: parent.width
height: units.gu(10)
- triggerActionOnMouseRelease: true
onItemClicked: {
print("selected tag:", model.guid)
=== modified file 'src/libqtevernote/evernoteconnection.cpp'
--- src/libqtevernote/evernoteconnection.cpp 2015-03-09 10:03:03 +0000
+++ src/libqtevernote/evernoteconnection.cpp 2015-03-15 20:04:06 +0000
@@ -38,6 +38,7 @@
#include <Errors_types.h>
#include <QUrl>
+#include <QTime>
#include <libintl.h>
@@ -65,6 +66,9 @@
m_userStoreHttpClient(0)
{
qRegisterMetaType<EvernoteConnection::ErrorCode>("EvernoteConnection::ErrorCode");
+
+ m_reconnectTimer.setSingleShot(true);
+ connect(&m_reconnectTimer, &QTimer::timeout, this, &EvernoteConnection::connectToEvernote);
}
void EvernoteConnection::setupUserStore()
@@ -148,6 +152,10 @@
void EvernoteConnection::disconnectFromEvernote()
{
qCDebug(dcConnection) << "Disconnecting from Evernote.";
+
+ m_errorMessage.clear();
+ emit errorChanged();
+
if (!isConnected()) {
qCWarning(dcConnection()) << "Not connected. Can't disconnect.";
return;
@@ -314,6 +322,36 @@
emit errorChanged();
return false;
}
+ } catch (const evernote::edam::EDAMUserException &e) {
+ qCWarning(dcConnection) << "EDAMUserException getting note store path:" << e.what() << "EDAM Error Code:" << e.errorCode;
+ switch (e.errorCode) {
+ case evernote::edam::EDAMErrorCode::AUTH_EXPIRED:
+ m_errorMessage = gettext("Authentication for Evernote server expired. Please renew login information in the accounts settings.");
+ break;
+ default:
+ m_errorMessage = QString(gettext("Unknown error connecting to Evernote: %1")).arg(e.errorCode);
+ break;
+ }
+ emit errorChanged();
+ return false;
+ } catch (const evernote::edam::EDAMSystemException &e) {
+ qCWarning(dcConnection) << "EDAMSystemException getting note store path:" << e.what() << e.errorCode;
+ switch (e.errorCode) {
+ case evernote::edam::EDAMErrorCode::RATE_LIMIT_REACHED:
+ m_errorMessage = gettext("Error connecting to Evernote: Rate limit exceeded. Please try again later.");
+ m_reconnectTimer.stop();
+ m_reconnectTimer.start(e.rateLimitDuration * 1000);
+ {
+ QTime time = QTime::fromMSecsSinceStartOfDay(e.rateLimitDuration * 1000);
+ qCDebug(dcConnection) << "Cannot connect. Rate limit exceeded. Reconnecting in" << time.toString("mm:ss");
+ }
+ break;
+ default:
+ m_errorMessage = gettext("Unknown error connecting to Evernote: %1");
+ break;
+ }
+ emit errorChanged();
+ return false;
} catch (const TTransportException & e) {
qCWarning(dcConnection) << "Failed to fetch notestore path:" << e.what();
m_errorMessage = QString(gettext("Error connecting to Evernote: Connection failure when downloading server information."));
@@ -321,7 +359,7 @@
return false;
} catch (const TException & e) {
qCWarning(dcConnection) << "Generic Thrift exception when fetching notestore path:" << e.what();
- m_errorMessage = gettext("Unknown error connecting to Evernote");
+ m_errorMessage = gettext("Unknown error connecting to Evernote.");
emit errorChanged();
return false;
}
=== modified file 'src/libqtevernote/evernoteconnection.h'
--- src/libqtevernote/evernoteconnection.h 2015-03-08 18:47:51 +0000
+++ src/libqtevernote/evernoteconnection.h 2015-03-15 20:04:06 +0000
@@ -28,6 +28,7 @@
#include <transport/THttpClient.h>
#include <QObject>
+#include <QTimer>
namespace evernote {
namespace edam {
@@ -141,6 +142,8 @@
evernote::edam::UserStoreClient *m_userstoreClient;
boost::shared_ptr<THttpClient> m_userStoreHttpClient;
+
+ QTimer m_reconnectTimer;
};
#endif // EVERNOTECONNECTION_H
=== modified file 'src/libqtevernote/jobs/evernotejob.cpp'
--- src/libqtevernote/jobs/evernotejob.cpp 2015-03-08 18:47:51 +0000
+++ src/libqtevernote/jobs/evernotejob.cpp 2015-03-15 20:04:06 +0000
@@ -100,6 +100,7 @@
}
} catch (const evernote::edam::EDAMUserException &e) {
QString message;
+ EvernoteConnection::ErrorCode errorCode = EvernoteConnection::ErrorCodeUserException;
switch (e.errorCode) {
case evernote::edam::EDAMErrorCode::UNKNOWN:
message = "Unknown Error: %1";
@@ -118,15 +119,18 @@
break;
case evernote::edam::EDAMErrorCode::LIMIT_REACHED:
message = "Limit reached: %1";
+ errorCode = EvernoteConnection::ErrorCodeLimitExceeded;
break;
case evernote::edam::EDAMErrorCode::QUOTA_REACHED:
message = "Quota reached: %1";
+ errorCode = EvernoteConnection::ErrorCodeQutaExceeded;
break;
case evernote::edam::EDAMErrorCode::INVALID_AUTH:
message = "Invalid auth: %1";
break;
case evernote::edam::EDAMErrorCode::AUTH_EXPIRED:
message = "Auth expired: %1";
+ errorCode = EvernoteConnection::ErrorCodeAuthExpired;
break;
case evernote::edam::EDAMErrorCode::DATA_CONFLICT:
message = "Data conflict: %1";
@@ -161,26 +165,26 @@
}
message = message.arg(QString::fromStdString(e.parameter));
qCWarning(dcJobQueue) << metaObject()->className() << "EDAMUserException:" << message;
- emitJobDone(EvernoteConnection::ErrorCodeUserException, message);
+ emitJobDone(errorCode, message);
} catch (const evernote::edam::EDAMSystemException &e) {
qCWarning(dcJobQueue) << "EDAMSystemException in" << metaObject()->className() << e.what() << e.errorCode << QString::fromStdString(e.message);
QString message;
EvernoteConnection::ErrorCode errorCode;
switch (e.errorCode) {
case evernote::edam::EDAMErrorCode::AUTH_EXPIRED:
- message = gettext("Authentication expired.");
+ message = "Authentication expired.";
errorCode = EvernoteConnection::ErrorCodeAuthExpired;
break;
case evernote::edam::EDAMErrorCode::LIMIT_REACHED:
- message = gettext("Limit exceeded.");
+ message = "Limit exceeded.";
errorCode = EvernoteConnection::ErrorCodeLimitExceeded;
break;
case evernote::edam::EDAMErrorCode::RATE_LIMIT_REACHED:
- message = gettext("Rate limit exceeded.");
+ message = "Rate limit exceeded.";
errorCode = EvernoteConnection::ErrorCodeRateLimitExceeded;
break;
case evernote::edam::EDAMErrorCode::QUOTA_REACHED:
- message = gettext("Quota exceeded.");
+ message = "Quota exceeded.";
errorCode = EvernoteConnection::ErrorCodeQutaExceeded;
break;
default:
=== modified file 'src/libqtevernote/jobs/fetchnotejob.cpp'
--- src/libqtevernote/jobs/fetchnotejob.cpp 2015-02-26 22:47:10 +0000
+++ src/libqtevernote/jobs/fetchnotejob.cpp 2015-03-15 20:04:06 +0000
@@ -20,12 +20,13 @@
#include "fetchnotejob.h"
-FetchNoteJob::FetchNoteJob(const QString &guid, LoadWhat what, QObject *parent) :
+FetchNoteJob::FetchNoteJob(const QString &guid, LoadWhatFlags what, QObject *parent) :
NotesStoreJob(parent),
m_guid(guid),
m_what(what)
{
qRegisterMetaType<LoadWhat>("LoadWhat");
+ qRegisterMetaType<LoadWhatFlags>("LoadWhatFlags");
}
bool FetchNoteJob::operator==(const EvernoteJob *other) const
@@ -53,7 +54,9 @@
void FetchNoteJob::startJob()
{
- client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), m_what == LoadContent, m_what == LoadResources, false, false);
+ // Just in case we error out, make sure the reply can be idenfied by note guid
+ m_result.guid = m_guid.toStdString();
+ client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), m_what.testFlag(LoadContent), m_what.testFlag(LoadResources), false, false);
}
void FetchNoteJob::emitJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage)
=== modified file 'src/libqtevernote/jobs/fetchnotejob.h'
--- src/libqtevernote/jobs/fetchnotejob.h 2015-02-26 22:47:10 +0000
+++ src/libqtevernote/jobs/fetchnotejob.h 2015-03-15 20:04:06 +0000
@@ -28,18 +28,19 @@
Q_OBJECT
public:
enum LoadWhat {
- LoadContent,
- LoadResources
+ LoadContent = 0x01,
+ LoadResources = 0x02
};
+ Q_DECLARE_FLAGS(LoadWhatFlags, LoadWhat)
- explicit FetchNoteJob(const QString &guid, LoadWhat what, QObject *parent = 0);
+ explicit FetchNoteJob(const QString &guid, LoadWhatFlags what, QObject *parent = 0);
virtual bool operator==(const EvernoteJob *other) const override;
virtual void attachToDuplicate(const EvernoteJob *other) override;
virtual QString toString() const override;
signals:
- void resultReady(EvernoteConnection::ErrorCode error, const QString &errorMessage, const evernote::edam::Note ¬e, LoadWhat what);
+ void resultReady(EvernoteConnection::ErrorCode error, const QString &errorMessage, const evernote::edam::Note ¬e, LoadWhatFlags what);
protected:
void startJob();
@@ -49,7 +50,7 @@
evernote::edam::NoteStoreClient *m_client;
QString m_token;
QString m_guid;
- LoadWhat m_what;
+ LoadWhatFlags m_what;
evernote::edam::Note m_result;
=== modified file 'src/libqtevernote/jobs/savenotejob.cpp'
--- src/libqtevernote/jobs/savenotejob.cpp 2015-03-06 00:47:45 +0000
+++ src/libqtevernote/jobs/savenotejob.cpp 2015-03-15 20:04:06 +0000
@@ -76,6 +76,9 @@
note.tagGuids = tags;
note.__isset.tagGuids = true;
+ note.active = true;
+ note.__isset.active = true;
+
note.__isset.attributes = true;
note.attributes.reminderOrder = m_note->reminderOrder();
note.attributes.__isset.reminderOrder = true;
=== modified file 'src/libqtevernote/note.cpp'
--- src/libqtevernote/note.cpp 2015-03-09 09:57:36 +0000
+++ src/libqtevernote/note.cpp 2015-03-15 20:04:06 +0000
@@ -41,7 +41,8 @@
m_loaded(false),
m_needsContentSync(false),
m_syncError(false),
- m_conflicting(false)
+ m_conflicting(false),
+ m_conflictingNote(nullptr)
{
setGuid(guid);
m_cacheFile.setFileName(NotesStore::instance()->storageLocation() + "note-" + guid + ".enml");
@@ -471,6 +472,11 @@
return m_resources.values();
}
+Note *Note::conflictingNote() const
+{
+ return m_conflictingNote;
+}
+
QStringList Note::resourceUrls() const
{
QList<QString> ret;
@@ -531,7 +537,7 @@
return;
}
- Resource *resource = new Resource(fileName.path());
+ Resource *resource = new Resource(fileName.path(), this);
m_resources.insert(resource->hash(), resource);
m_content.attachFile(position, resource->hash(), resource->type());
emit resourcesChanged();
@@ -733,3 +739,15 @@
emit conflictingChanged();
}
}
+
+void Note::setConflictingNote(Note *note)
+{
+ if (m_conflictingNote) {
+ m_conflictingNote->deleteLater();
+ }
+ m_conflictingNote = note;
+ if (m_conflictingNote) {
+ m_conflictingNote->setParent(this);
+ }
+ emit conflictingNoteChanged();
+}
=== modified file 'src/libqtevernote/note.h'
--- src/libqtevernote/note.h 2015-03-09 09:57:36 +0000
+++ src/libqtevernote/note.h 2015-03-15 20:04:06 +0000
@@ -67,6 +67,7 @@
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
Q_PROPERTY(bool synced READ synced NOTIFY syncedChanged)
Q_PROPERTY(bool syncError READ syncError NOTIFY syncErrorChanged)
+ Q_PROPERTY(Note* conflictingNote READ conflictingNote NOTIFY conflictingNoteChanged)
// When asking the note's richTextContent, usually the embedded images will have their original size.
// For rendering that content in a WebView or TextEdit, that might not be appropriate as images might
@@ -158,6 +159,7 @@
Q_INVOKABLE Resource* resource(const QString &hash);
QList<Resource*> resources() const;
+ Note *conflictingNote() const;
Q_INVOKABLE void markTodo(const QString &todoId, bool checked);
Q_INVOKABLE void attachFile(int position, const QUrl &fileName);
@@ -194,6 +196,7 @@
void syncedChanged();
void syncErrorChanged();
void conflictingChanged();
+ void conflictingNoteChanged();
void renderWidthChanged();
@@ -212,6 +215,7 @@
void setUpdateSequenceNumber(qint32 updateSequenceNumber);
void setLastSyncedSequenceNumber(qint32 lastSyncedSequenceNumber);
void setConflicting(bool conflicting);
+ void setConflictingNote(Note *serverNote);
Resource *addResource(const QString &hash, const QString &fileName, const QString &type, const QByteArray &data = QByteArray());
void addMissingResource();
void setMissingResources(int missingResources);
@@ -245,6 +249,8 @@
bool m_syncError;
bool m_conflicting;
+ Note *m_conflictingNote;
+
// Needed to be able to call private setLoading (we don't want to have that set by anyone except the NotesStore)
friend class NotesStore;
};
=== modified file 'src/libqtevernote/notes.cpp'
--- src/libqtevernote/notes.cpp 2015-03-08 18:51:50 +0000
+++ src/libqtevernote/notes.cpp 2015-03-15 20:04:06 +0000
@@ -25,7 +25,7 @@
QSortFilterProxyModel(parent),
m_onlyReminders(false),
m_onlySearchResults(false),
- m_showDeleted(false),
+ m_showDeleted(true),
m_sortOrder(SortOrderDateUpdatedNewest)
{
connect(NotesStore::instance(), &NotesStore::loadingChanged, this, &Notes::loadingChanged);
=== modified file 'src/libqtevernote/notesstore.cpp'
--- src/libqtevernote/notesstore.cpp 2015-03-09 09:57:36 +0000
+++ src/libqtevernote/notesstore.cpp 2015-03-15 20:04:06 +0000
@@ -307,9 +307,11 @@
notebook->setLoading(false);
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "Error creating notebook:" << errorMessage;
notebook->setSyncError(true);
+ emit notebookChanged(notebook->guid());
return;
}
QString guid = QString::fromStdString(result.guid);
@@ -503,6 +505,8 @@
}
tag->setLoading(false);
+
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "Error creating tag on server:" << errorMessage;
tag->setSyncError(true);
@@ -540,6 +544,8 @@
return;
}
tag->setLoading(false);
+
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "Error updating tag on server" << errorMessage;
tag->setSyncError(true);
@@ -625,26 +631,8 @@
void NotesStore::fetchNotesJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::NotesMetadataList &results, const QString &filterNotebookGuid)
{
- switch (errorCode) {
- case EvernoteConnection::ErrorCodeNoError:
- // All is well...
- break;
- case EvernoteConnection::ErrorCodeUserException:
- qCWarning(dcSync) << "FetchNotesJobDone: EDAMUserException:" << errorMessage;
- m_loading = false;
- emit loadingChanged();
- return; // silently discarding...
- case EvernoteConnection::ErrorCodeConnectionLost:
- qCWarning(dcSync) << "FetchNotesJobDone: Connection with evernote lost:" << errorMessage;
- m_loading = false;
- emit loadingChanged();
- return; // silently discarding...
- case EvernoteConnection::ErrorCodeNotFoundExcpetion:
- qCWarning(dcSync) << "FetchNotesJobDone: Item not found on server:" << errorMessage;
- m_loading = false;
- emit loadingChanged();
- return; // silently discarding...
- default:
+ handleUserError(errorCode);
+ if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "FetchNotesJobDone: Failed to fetch notes list:" << errorMessage << errorCode;
m_loading = false;
emit loadingChanged();
@@ -702,6 +690,15 @@
qCWarning(dcSync) << "remote sequence:" << result.updateSequenceNum;
note->setConflicting(true);
changedRoles << RoleConflicting;
+
+ // Not setting parent as we don't want to squash the reply.
+ FetchNoteJob::LoadWhatFlags flags = 0x0;
+ flags |= FetchNoteJob::LoadContent;
+ flags |= FetchNoteJob::LoadResources;
+ FetchNoteJob *fetchNoteJob = new FetchNoteJob(note->guid(), flags);
+ fetchNoteJob->setJobPriority(EvernoteJob::JobPriorityMedium);
+ connect(fetchNoteJob, &FetchNoteJob::resultReady, this, &NotesStore::fetchConflictingNoteJobDone);
+ EvernoteConnection::instance()->enqueue(fetchNoteJob);
}
}
@@ -721,7 +718,7 @@
qCDebug(dcSync) << "Not all notes fetched yet. Fetching next batch.";
refreshNotes(filterNotebookGuid, results.startIndex + results.notes.size());
} else {
- qCDebug(dcSync) << "Fetched all notes from Evernote. Starting merge of local changes...";
+ qCDebug(dcSync) << "Fetched all notes from Evernote. Starting sync of local-only notes.";
m_organizerAdapter->startSync();
m_loading = false;
emit loadingChanged();
@@ -767,9 +764,14 @@
connect(job, &CreateNoteJob::jobDone, this, &NotesStore::createNoteJobDone);
EvernoteConnection::instance()->enqueue(job);
} else {
- // This note has been deleted from the server... drop it
int idx = m_notes.indexOf(note);
- if (idx > -1) {
+ if (idx == -1) {
+ qCWarning(dcSync) << "Should sync unhandled note but it is gone by now...";
+ continue;
+ }
+
+ if (note->synced()) {
+ qCDebug(dcSync) << "Note has been deleted from the server and not changed locally. Deleting local note:" << note->guid();
beginRemoveRows(QModelIndex(), idx, idx);
m_notes.removeAt(idx);
m_notesHash.remove(note->guid());
@@ -783,10 +785,21 @@
settings.endGroup();
note->deleteLater();
+ } else {
+ qCDebug(dcSync) << "CONFLICT: Note has been deleted from the server but we have unsynced local changes for note:" << note->guid();
+ FetchNoteJob::LoadWhatFlags flags = 0x0;
+ flags |= FetchNoteJob::LoadContent;
+ flags |= FetchNoteJob::LoadResources;
+ FetchNoteJob *job = new FetchNoteJob(note->guid(), flags);
+ connect(job, &FetchNoteJob::resultReady, this, &NotesStore::fetchConflictingNoteJobDone);
+ EvernoteConnection::instance()->enqueue(job);
+
+ note->setConflicting(true);
+ emit dataChanged(index(idx), index(idx), QVector<int>() << RoleConflicting);
}
}
}
- qCDebug(dcSync) << "Local changes merged.";
+ qCDebug(dcSync) << "Local-only notes synced.";
}
}
@@ -812,7 +825,7 @@
}
}
-void NotesStore::fetchNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhat what)
+void NotesStore::fetchNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what)
{
FetchNoteJob *job = static_cast<FetchNoteJob*>(sender());
Note *note = m_notesHash.value(QString::fromStdString(result.guid));
@@ -824,24 +837,10 @@
QModelIndex noteIndex = index(m_notes.indexOf(note));
QVector<int> roles;
- switch (errorCode) {
- case EvernoteConnection::ErrorCodeNoError:
- // All is well
- break;
- case EvernoteConnection::ErrorCodeUserException:
- qCWarning(dcSync) << "FetchNoteJobDone: EDAMUserException:" << errorMessage;
- emit dataChanged(noteIndex, noteIndex, roles);
- return; // silently discarding...
- case EvernoteConnection::ErrorCodeConnectionLost:
- qCWarning(dcSync) << "FetchNoteJobDone: Connection with evernote lost:" << errorMessage;
- emit dataChanged(noteIndex, noteIndex, roles);
- return; // silently discarding...
- case EvernoteConnection::ErrorCodeNotFoundExcpetion:
- qCWarning(dcSync) << "FetchNoteJobDone: Item not found on server:" << errorMessage;
- emit dataChanged(noteIndex, noteIndex, roles);
- return; // silently discarding...
- default:
- qCWarning(dcSync) << "FetchNoteJobDone: Failed to fetch note content:" << errorMessage << errorCode;
+ handleUserError(errorCode);
+ if (errorCode != EvernoteConnection::ErrorCodeNoError) {
+ note->setLoading(false);
+ roles << RoleLoading;
note->setSyncError(true);
roles << RoleSyncError;
emit dataChanged(noteIndex, noteIndex, roles);
@@ -932,6 +931,52 @@
note->syncToCacheFile(); // Syncs note's content into notes cache
}
+void NotesStore::fetchConflictingNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what)
+{
+ Note *note = m_notesHash.value(QString::fromStdString(result.guid));
+ if (!note) {
+ qCWarning(dcSync) << "Fetched conflicting note from server but local note can't be found any more:" << QString::fromStdString(result.guid);
+ return;
+ }
+
+ handleUserError(errorCode);
+ if (errorCode != EvernoteConnection::ErrorCodeNoError) {
+ qCWarning(dcSync) << "Failed to fetch conflicting note for" << note->guid();
+ return;
+ }
+
+ qCDebug(dcSync) << "Fetched conflicting note:" << note->guid();
+
+ // Make sure local note is loaded
+ note->loadFromCacheFile();
+
+ Note *serverNote = new Note("conflict-" + note->guid(), result.updateSequenceNum, note);
+ serverNote->setUpdateSequenceNumber(result.updateSequenceNum);
+ serverNote->setLastSyncedSequenceNumber(result.updateSequenceNum);
+ serverNote->setTitle(QString::fromStdString(result.title));
+ serverNote->setNotebookGuid(QString::fromStdString(result.notebookGuid));
+ serverNote->setUpdated(QDateTime::fromMSecsSinceEpoch(result.updated));
+ serverNote->setDeleted(result.deleted > 0);
+ QStringList tagGuids;
+ foreach (const std::string &guid, result.tagGuids) {
+ tagGuids << QString::fromStdString(guid);
+ }
+ serverNote->setTagGuids(tagGuids);
+ serverNote->setReminderOrder(result.attributes.reminderOrder);
+ serverNote->setReminderTime(QDateTime::fromMSecsSinceEpoch(result.attributes.reminderTime));
+ serverNote->setReminderDoneTime(QDateTime::fromMSecsSinceEpoch(result.attributes.reminderDoneTime));
+
+ serverNote->setEnmlContent(QString::fromStdString(result.content));
+
+ foreach (const evernote::edam::Resource &resource, result.resources) {
+ serverNote->addResource(QString::fromStdString(resource.data.bodyHash), QString::fromStdString(resource.attributes.fileName), QString::fromStdString(resource.mime));
+ }
+
+ note->setConflictingNote(serverNote);
+ note->setLoading(false);
+
+}
+
void NotesStore::refreshNotebooks()
{
if (!EvernoteConnection::instance()->isConnected()) {
@@ -951,20 +996,10 @@
m_notebooksLoading = false;
emit notebooksLoadingChanged();
- switch (errorCode) {
- case EvernoteConnection::ErrorCodeNoError:
- // All is well...
- break;
- case EvernoteConnection::ErrorCodeUserException:
- qCWarning(dcSync) << "FetchNotebooksJobDone: EDAMUserException:" << errorMessage;
- // silently discarding...
- return;
- case EvernoteConnection::ErrorCodeConnectionLost:
- qCWarning(dcSync) << "FetchNotebooksJobDone: Connection lost:" << errorMessage;
- return; // silently discarding
- default:
+ handleUserError(errorCode);
+ if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "FetchNotebooksJobDone: Failed to fetch notes list:" << errorMessage << errorCode;
- return; // silently discarding
+ return;
}
QList<Notebook*> unhandledNotebooks = m_notebooks;
@@ -1063,20 +1098,10 @@
m_tagsLoading = false;
emit tagsLoadingChanged();
- switch (errorCode) {
- case EvernoteConnection::ErrorCodeNoError:
- // All is well...
- break;
- case EvernoteConnection::ErrorCodeUserException:
- qCWarning(dcSync) << "FetchTagsJobDone: EDAMUserException:" << errorMessage;
- // silently discarding...
- return;
- case EvernoteConnection::ErrorCodeConnectionLost:
- qCWarning(dcSync) << "FetchTagsJobDone: Connection lost:" << errorMessage;
- return; // silently discarding
- default:
+ handleUserError(errorCode);
+ if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "FetchTagsJobDone: Failed to fetch notes list:" << errorMessage << errorCode;
- return; // silently discarding
+ return;
}
QHash<QString, Tag*> unhandledTags = m_tagsHash;
@@ -1203,6 +1228,7 @@
note->setLoading(false);
roles << RoleLoading;
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "Error creating note on server:" << tmpGuid << errorMessage;
note->setSyncError(true);
@@ -1266,6 +1292,7 @@
qCWarning(dcNotesStore) << "Can't save note. Guid not found:" << guid;
return;
}
+ qCDebug(dcNotesStore) << "Saving note. Setting updateSequenceNumber to:" << note->updateSequenceNumber()+1;
note->setUpdateSequenceNumber(note->updateSequenceNumber()+1);
note->setUpdated(QDateTime::currentDateTime());
syncToCacheFile(note);
@@ -1304,13 +1331,13 @@
int idx = m_notes.indexOf(note);
note->setLoading(false);
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
- qCWarning(dcSync) << "Error saving note:" << errorMessage;
+ qCWarning(dcSync) << "Unhandled error saving note:" << errorCode << "Message:" << errorMessage;
note->setSyncError(true);
emit dataChanged(index(idx), index(idx), QVector<int>() << RoleLoading << RoleSyncError);
return;
}
- note->setSyncError(false);
note->setUpdateSequenceNumber(result.updateSequenceNum);
note->setLastSyncedSequenceNumber(result.updateSequenceNum);
@@ -1327,19 +1354,25 @@
void NotesStore::saveNotebookJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Notebook &result)
{
- if (errorCode != EvernoteConnection::ErrorCodeNoError) {
- qCWarning(dcSync) << "Error saving notebook to server" << errorMessage;
- return;
- }
-
Notebook *notebook = m_notebooksHash.value(QString::fromStdString(result.guid));
if (!notebook) {
qCWarning(dcSync) << "Save notebook job done but notebook can't be found any more!";
return;
}
+
+ handleUserError(errorCode);
+ if (errorCode != EvernoteConnection::ErrorCodeNoError) {
+ qCWarning(dcSync) << "Error saving notebook to server" << errorCode << errorMessage;
+ notebook->setSyncError(true);
+ emit notebookChanged(notebook->guid());
+ return;
+ }
+
+ notebook->setLoading(false);
+ notebook->setSyncError(false);
+
qCDebug(dcSync) << "Notebooks saved to server:" << notebook->guid();
updateFromEDAM(result, notebook);
- notebook->setLoading(false);
emit notebookChanged(notebook->guid());
syncToCacheFile(notebook);
}
@@ -1410,6 +1443,7 @@
void NotesStore::deleteNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const QString &guid)
{
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "Cannot delete note from server:" << errorMessage;
return;
@@ -1430,6 +1464,7 @@
void NotesStore::expungeNotebookJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const QString &guid)
{
+ handleUserError(errorCode);
if (errorCode != EvernoteConnection::ErrorCodeNoError) {
qCWarning(dcSync) << "Error expunging notebook:" << errorMessage;
return;
@@ -1643,6 +1678,25 @@
notebook->setLastSyncedSequenceNumber(evNotebook.updateSequenceNum);
}
+bool NotesStore::handleUserError(EvernoteConnection::ErrorCode errorCode)
+{
+ switch (errorCode) {
+ case EvernoteConnection::ErrorCodeAuthExpired:
+ m_errorQueue.append(gettext("Authentication for Evernote server expired. Please renew login information in the accounts settings."));
+ break;
+ case EvernoteConnection::ErrorCodeLimitExceeded:
+ m_errorQueue.append(gettext("Rate limit for Evernote server exceeded. Please try again later."));
+ break;
+ case EvernoteConnection::ErrorCodeQutaExceeded:
+ m_errorQueue.append(gettext("Upload quota for Evernote server exceed. Please try again later."));
+ break;
+ default:
+ return false;
+ }
+ emit errorChanged();
+ return true;
+}
+
void NotesStore::expungeTag(const QString &guid)
{
@@ -1682,3 +1736,35 @@
tag->deleteInfoFile();
tag->deleteLater();
}
+
+void NotesStore::resolveConflict(const QString ¬eGuid, NotesStore::ConflictResolveMode mode)
+{
+ Note *note = m_notesHash.value(noteGuid);
+ if (!note) {
+ qCWarning(dcNotesStore) << "Should resolve a conflict but can't find note for guid:" << noteGuid;
+ return;
+ }
+ if (!note->conflictingNote()) {
+ qCWarning(dcNotesStore) << "Should resolve a conflict but note doesn't have a conflicting note:" << noteGuid;
+ return;
+ }
+ if (mode == KeepLocal) {
+ qCDebug(dcNotesStore) << "Resolving conflict using local note for note guid:" << noteGuid;
+ note->setUpdateSequenceNumber(note->conflictingNote()->updateSequenceNumber() + 1);
+ note->setConflicting(false);
+ saveNote(note->guid());
+ } else {
+ qCDebug(dcNotesStore) << "Resolving conflict using remote note for note guid:" << noteGuid;
+ Note *newNote = note->conflictingNote();
+ newNote->setParent(this);
+ // Conflicting notes have their guid prefixed, lets correct that
+ newNote->setGuid(note->guid());
+ newNote->setConflicting(false);
+ int idx = m_notes.indexOf(note);
+ m_notesHash[note->guid()] = newNote;
+ m_notes.replace(idx, newNote);
+ emit noteChanged(newNote->guid(), newNote->notebookGuid());
+ emit dataChanged(index(idx), index(idx));
+ saveNote(note->guid());
+ }
+}
=== modified file 'src/libqtevernote/notesstore.h'
--- src/libqtevernote/notesstore.h 2015-03-04 00:23:45 +0000
+++ src/libqtevernote/notesstore.h 2015-03-15 20:04:06 +0000
@@ -51,6 +51,7 @@
class NotesStore : public QAbstractListModel
{
Q_OBJECT
+ Q_ENUMS(ConflictResolveMode)
// Setting the username will cause notes to be loaded from cache
// If you want to load the local cache only (not associated with an Evernote account), make sure to set this to ""
// Note that if you use EvernoteConnection to log in (by setting a token obtained from OA) this username
@@ -91,6 +92,11 @@
RoleConflicting
};
+ enum ConflictResolveMode {
+ KeepLocal,
+ KeepRemote
+ };
+
~NotesStore();
static NotesStore *instance();
@@ -137,6 +143,8 @@
Q_INVOKABLE void untagNote(const QString ¬eGuid, const QString &tagGuid);
Q_INVOKABLE void expungeTag(const QString &guid);
+ Q_INVOKABLE void resolveConflict(const QString ¬eGuid, ConflictResolveMode mode);
+
public slots:
void refreshNotes(const QString &filterNotebookGuid = QString(), int startIndex = 0);
@@ -178,7 +186,8 @@
private slots:
void fetchNotesJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::NotesMetadataList &results, const QString &filterNotebookGuid);
void fetchNotebooksJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const std::vector<evernote::edam::Notebook> &results);
- void fetchNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhat what);
+ void fetchNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what);
+ void fetchConflictingNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what);
void createNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const QString &tmpGuid, const evernote::edam::Note &result);
void saveNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result);
void saveNotebookJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Notebook &result);
@@ -203,6 +212,8 @@
QVector<int> updateFromEDAM(const evernote::edam::NoteMetadata &evNote, Note *note);
void updateFromEDAM(const evernote::edam::Notebook &evNotebook, Notebook *notebook);
+ bool handleUserError(EvernoteConnection::ErrorCode errorCode);
+
private:
explicit NotesStore(QObject *parent = 0);
static NotesStore *s_instance;
Follow ups
-
[Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: noreply, 2015-03-31
-
[Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Riccardo Padovani, 2015-03-31
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Riccardo Padovani, 2015-03-31
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Ubuntu Phone Apps Jenkins Bot, 2015-03-30
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Michael Zanetti, 2015-03-30
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Riccardo Padovani, 2015-03-30
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Alan Pope , 2015-03-26
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Ubuntu Phone Apps Jenkins Bot, 2015-03-18
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Ubuntu Phone Apps Jenkins Bot, 2015-03-18
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Michael Zanetti, 2015-03-17
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Riccardo Padovani, 2015-03-17
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Ubuntu Phone Apps Jenkins Bot, 2015-03-15
-
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
From: Ubuntu Phone Apps Jenkins Bot, 2015-03-15