← Back to team overview

ubuntu-touch-coreapps-reviewers team mailing list archive

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 &note, LoadWhat what);
> +    void resultReady(EvernoteConnection::ErrorCode error, const QString &errorMessage, const evernote::edam::Note &note, 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 &noteGuid, 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 &noteGuid, const QString &tagGuid);
>      Q_INVOKABLE void expungeTag(const QString &guid);
>  
> +    Q_INVOKABLE void resolveConflict(const QString &noteGuid, 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