ubuntu-touch-coreapps-reviewers team mailing list archive
-
ubuntu-touch-coreapps-reviewers team
-
Mailing list archive
-
Message #01141
Re: [Merge] lp:~mzanetti/reminders-app/conflict-handling into lp:reminders-app
Code looks good to me, I left a couple of comments, but are minor issues.
Also, I have 3 warnings while compiling:
/home/rpadovani/Documents/ubuntu/touch/coreapps/reminders/conflict-handling/src/libqtevernote/notesstore.cpp:828:91: warning: unused parameter ‘errorMessage’ [-Wunused-parameter]
void NotesStore::fetchNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what)
^
/home/rpadovani/Documents/ubuntu/touch/coreapps/reminders/conflict-handling/src/libqtevernote/notesstore.cpp:934:102: warning: unused parameter ‘errorMessage’ [-Wunused-parameter]
void NotesStore::fetchConflictingNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what)
^
/home/rpadovani/Documents/ubuntu/touch/coreapps/reminders/conflict-handling/src/libqtevernote/notesstore.cpp:934:180: warning: unused parameter ‘what’ [-Wunused-parameter]
void NotesStore::fetchConflictingNoteJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage, const evernote::edam::Note &result, FetchNoteJob::LoadWhatFlags what)
Diff comments:
> === 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-17 23:49:46 +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 {
Why do you use RowLayout when you don't use any of its properties in children?
> + Label {
> + text: i18n.tr("Notebook:")
> + }
> + Label {
> + text: root.note ? NotesStore.notebook(root.note.notebookGuid).name : ""
> + }
> + }
> +
> + NotesDelegate {
> + Layout.fillWidth: true
> +
> + title: root.note.title
It shouldn't be
title: root.note ? root.note.title : ''
(also for all other properties?)
> + 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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +0000
> @@ -109,20 +109,56 @@
> pagestack.push(accountPage);
> }
>
> - function displayNote(note) {
> + function displayNote(note, conflictMode) {
> + if (conflictMode == undefined) {
> + conflictMode = false;
> + }
> +
> 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) {
> + // User wants to open the note 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)
I think you want to drop this one?
> + 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;
You already assigned note to the page in conflict page (page = component.createObject(root, {note: note});), doesn't this decrease performances?
> - 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 (!conflictMode && note.conflicting) {
> + // User wants to open the note even though it is conflicting! Show the Conflict page instead.
> + notesPage.conflictMode = true;
> + view = sideViewLoader.embed(Qt.resolvedUrl("ui/NoteConflictView.qml"))
> + view.displayNote.connect(function(note) {root.displayNote(note,true)})
> + view.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)
Did you forget this?
> + NotesStore.resolveConflict(note.guid, keepLocal ? NotesStore.KeepLocal : NotesStore.KeepRemote);
> + });
> + })
> + } else {
> + notesPage.conflictMode = conflictMode;
> + view = sideViewLoader.embed(Qt.resolvedUrl("ui/NoteView.qml"))
> + view.openTaggedNotes.connect(function(title, tagGuid) {root.openTaggedNotes(title, tagGuid, false)})
> + }
> view.note = note;
You already assigned note to the view in the case of conflict page
> }
> }
> @@ -459,6 +495,8 @@
> objectName: "NotesTab"
> page: NotesPage {
> id: notesPage
> + readOnly: conflictMode
> + property bool conflictMode: false
>
> narrowMode: root.narrowMode
>
>
> === 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-17 23:49:46 +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)
Mhh, what's keepLocal?
Shouldn't you explicity declare the property?
> + }
> +}
>
> === 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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +0000
> @@ -29,6 +29,7 @@
> id: root
>
> property var selectedNote: null
> + property bool readOnly: false
> property bool narrowMode
>
> property alias filterNotebookGuid: notes.filterNotebookGuid
> @@ -99,7 +100,7 @@
> action: Action {
> text: i18n.tr("Delete")
> iconName: "delete"
> - visible: root.selectedNote !== null
> + visible: root.selectedNote !== null && !root.readOnly
> onTriggered: {
> NotesStore.deleteNote(root.selectedNote.guid);
> }
> @@ -114,7 +115,7 @@
> iconSource: root.selectedNote.reminder ?
> Qt.resolvedUrl("/usr/share/icons/suru/actions/scalable/reminder.svg") :
> Qt.resolvedUrl("/usr/share/icons/suru/actions/scalable/reminder-new.svg")
> - visible: root.selectedNote !== null
> + visible: root.selectedNote !== null && !root.readOnly
> onTriggered: {
> root.selectedNote.reminder = !root.selectedNote.reminder
> NotesStore.saveNote(root.selectedNote.guid)
> @@ -125,7 +126,7 @@
> action: Action {
> text: i18n.tr("Edit")
> iconName: "edit"
> - visible: root.selectedNote !== null
> + visible: root.selectedNote !== null && !root.readOnly
> onTriggered: {
> print("should edit note")
> root.editNote(root.selectedNote)
> @@ -161,7 +162,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 +176,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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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-17 23:49:46 +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,53 @@
> 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->setCreated(QDateTime::fromMSecsSinceEpoch(result.created));
> + 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 +997,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 +1099,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 +1229,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 +1293,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 +1332,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 +1355,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 +1444,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 +1465,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;
> @@ -1596,6 +1632,10 @@
> roles << RoleTagGuids;
> }
> }
> + if (evNote.__isset.attributes && evNote.attributes.__isset.reminderOrder) {
> + note->setReminderOrder(evNote.attributes.reminderOrder);
> + roles << RoleReminder;
> + }
> if (evNote.__isset.attributes && evNote.attributes.__isset.reminderTime) {
> QDateTime reminderTime;
> if (evNote.attributes.reminderTime > 0) {
> @@ -1616,6 +1656,10 @@
> roles << RoleReminderDoneTime;
> }
> }
> + if (evNote.__isset.deleted) {
> + note->setDeleted(evNote.deleted);
> + roles << RoleDeleted;
> + }
> note->setLastSyncedSequenceNumber(evNote.updateSequenceNum);
> return roles;
> }
> @@ -1643,6 +1687,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 +1745,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-17 23:49:46 +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;
>
--
https://code.launchpad.net/~mzanetti/reminders-app/conflict-handling/+merge/253002
Your team Ubuntu Reminders app developers is subscribed to branch lp:reminders-app.
References