summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2018-09-28 12:44:09 +0200
committerJohn Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>2018-09-28 12:44:09 +0200
commit1d47e704b1db4abe38c8f4ccdaed7474a972a2a8 (patch)
tree6b104504799cb009c79e0472b15b802df7b77923 /src
parent2f63616f5d43253f491e77ecdb7dabcbf220367f (diff)
Conflict management
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/ContactDetails.vue33
-rw-r--r--src/models/contact.js1
-rw-r--r--src/store/addressbooks.js5
-rw-r--r--src/store/contacts.js56
4 files changed, 81 insertions, 14 deletions
diff --git a/src/components/ContactDetails.vue b/src/components/ContactDetails.vue
index f72af6ff..57175048 100644
--- a/src/components/ContactDetails.vue
+++ b/src/components/ContactDetails.vue
@@ -71,7 +71,13 @@
<!-- actions -->
<div id="contact-header-actions">
- <div v-tooltip.auto="warning" :class="{'icon-loading-small': loadingUpdate, 'icon-error-white menu-icon--pulse': warning}" class="menu-icon" />
+ <div v-tooltip.bottom="warning" :class="{'icon-loading-small': loadingUpdate, 'menu-icon--pulse icon-error-white': warning}" class="menu-icon" />
+ <div v-tooltip="{
+ content: conflict,
+ show: true,
+ trigger: 'manual',
+ }" v-if="conflict" class="menu-icon menu-icon--pulse icon-history-white"
+ @click="refreshContact" />
<div class="menu-icon">
<div v-click-outside="closeMenu" class="icon-more-white" @click="toggleMenu" />
<div :class="{ 'open': openedMenu }" class="popovermenu">
@@ -167,7 +173,7 @@ export default {
/**
* Warning message
*
- * @returns {string}
+ * @returns {string|undefined}
*/
warning() {
if (!this.contact.dav) {
@@ -176,6 +182,17 @@ export default {
},
/**
+ * Conflict message
+ *
+ * @returns {string|undefined}
+ */
+ conflict() {
+ if (this.contact.conflict) {
+ return t('contacts', 'The contact you were trying to edit has changed. Please manually refresh the contact. Any further edits will be discarded.')
+ }
+ },
+
+ /**
* Contact color based on uid
*
* @returns {string}
@@ -336,7 +353,7 @@ export default {
// if contact exists AND if exists on server
if (contact && contact.dav) {
- this.$store.dispatch('fetchFullContact', contact)
+ this.$store.dispatch('fetchFullContact', { contact })
.then(() => {
// create empty contact and copy inner data
let localContact = new Contact(
@@ -391,6 +408,16 @@ export default {
this.updateContact()
})
}
+ },
+
+ /**
+ * Refresh the data of a contact
+ */
+ refreshContact() {
+ this.$store.dispatch('fetchFullContact', { contact: this.contact, etag: this.conflict })
+ .then(() => {
+ this.contact.conflict = false
+ })
}
}
}
diff --git a/src/models/contact.js b/src/models/contact.js
index 3836d91d..feed1a51 100644
--- a/src/models/contact.js
+++ b/src/models/contact.js
@@ -48,6 +48,7 @@ export default class Contact {
this.addressbook = addressbook
this.vCard = new ICAL.Component(this.jCal)
this.url = url
+ this.conflict = false
// if no uid set, create one
if (!this.vCard.hasProperty('uid')) {
diff --git a/src/store/addressbooks.js b/src/store/addressbooks.js
index d6ca8a1d..b7828f4b 100644
--- a/src/store/addressbooks.js
+++ b/src/store/addressbooks.js
@@ -306,7 +306,7 @@ const actions = {
// so we need to parse one by one
const contacts = response.map(item => {
let contact = new Contact(item.data, addressbook, item.url, item.etag)
- contact.dav = item
+ Vue.set(contact, 'dav', item)
return contact
})
context.commit('appendContactsToAddressbook', { addressbook, contacts })
@@ -345,6 +345,9 @@ const actions = {
// push contact to server and use limit
requests.push(limit(() => contact.addressbook.dav.createVCard(vData)
.then((response) => {
+ // setting the contact dav property
+ Vue.set(contact, 'dav', response)
+
// success, update store
context.commit('addContact', contact)
context.commit('addContactToAddressbook', contact)
diff --git a/src/store/contacts.js b/src/store/contacts.js
index aa0805f0..60e2c6bf 100644
--- a/src/store/contacts.js
+++ b/src/store/contacts.js
@@ -38,7 +38,7 @@ const mutations = {
* Store contacts into state
*
* @param {Object} state Default state
- * @param {Array} contacts Contacts
+ * @param {Array<Contact>} contacts Contacts
*/
appendContacts(state, contacts = []) {
state.contacts = contacts.reduce(function(list, contact) {
@@ -143,14 +143,15 @@ const mutations = {
},
/**
- * Update a contact
+ * Update a contact addressbook
*
* @param {Object} state the store data
+ * @param {Object} data destructuring object
* @param {Contact} contact the contact to update
+ * @param {Object} addressbook the addressbook to set
*/
updateContactAddressbook(state, { contact, addressbook }) {
if (state.contacts[contact.key] && contact instanceof Contact) {
-
// replace contact object data
state.contacts[contact.key].updateAddressbook(addressbook)
@@ -160,6 +161,24 @@ const mutations = {
},
/**
+ * Update a contact etag
+ *
+ * @param {Object} state the store data
+ * @param {Object} data destructuring object
+ * @param {Contact} contact the contact to update
+ * @param {string} etag the contact etag
+ */
+ updateContactEtag(state, { contact, etag }) {
+ if (state.contacts[contact.key] && contact instanceof Contact) {
+ // replace contact object data
+ state.contacts[contact.key].dav.etag = etag
+
+ } else {
+ console.error('Error while replacing the etag of following contact', contact)
+ }
+ },
+
+ /**
* Order the contacts list. Filters have terrible performances.
* We do not want to run the sorting function every time.
* Let's only run it on additions and create an index
@@ -254,22 +273,39 @@ const actions = {
.catch((error) => { throw error })
}
- contact.dav.data = vData
- return contact.dav.update()
- .then((response) => context.commit('updateContact', contact))
- .catch((error) => { throw error })
+ if (!contact.conflict) {
+ contact.dav.data = vData
+ return contact.dav.update()
+ .then((response) => {
+ // wrong etag, we most likely have a conflict
+ if (response.status === 412) {
+ contact.conflict = response.xhr.getResponseHeader('etag')
+ } else {
+ // all clear, let's update the store
+ context.commit('updateContact', contact)
+ }
+ })
+ .catch((error) => { throw error })
+ } else {
+ console.error('This contact is outdated, refusing to push', contact)
+ }
},
/**
* Fetch the full vCard from the dav server
*
* @param {Object} context the store mutations
- * @param {Contact} contact the contact to fetch
+ * @param {Object} data destructuring object
+ * @param {Contact} data.contact the contact to fetch
+ * @param {string} data.etag the contact etag
* @returns {Promise}
*/
- async fetchFullContact(context, contact) {
+ async fetchFullContact(context, { contact, etag = '' }) {
+ if (etag !== '') {
+ await context.commit('updateContactEtag', { contact, etag })
+ }
return contact.dav.fetchCompleteData()
- .then(() => {
+ .then((response) => {
let newContact = new Contact(contact.dav.data, contact.addressbook, contact.dav.url, contact.dav.etag)
context.commit('updateContact', newContact)
})