sync check items in background
This commit is contained in:
parent
ff77aa7268
commit
71ea67dc30
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<b-alert :show="!online" dismissible class="small float-up" variant="warning">{{ $t("OfflineAlert") }}</b-alert>
|
<b-alert :show="shopping_list_store.has_failed_items" dismissible class="float-up mt-2" variant="warning">
|
||||||
|
{{$t('ShoppingBackgroundSyncWarning')}}
|
||||||
|
</b-alert>
|
||||||
|
|
||||||
|
|
||||||
<div class="row float-top w-100">
|
<div class="row float-top w-100">
|
||||||
@ -578,33 +580,6 @@ export default {
|
|||||||
}
|
}
|
||||||
}, timeout)
|
}, timeout)
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* failed requests to sync entry check events are automatically re-queued by the service worker for sync
|
|
||||||
* this command allows to manually force replaying those events before re-enabling automatic sync
|
|
||||||
*/
|
|
||||||
replaySyncQueue: function () {
|
|
||||||
const wb = new Workbox('/service-worker.js');
|
|
||||||
wb.register();
|
|
||||||
wb.messageSW({type: 'BGSYNC_REPLAY_REQUESTS'}).then((r) => {
|
|
||||||
console.log('Background sync queue replayed!', r);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* get the number of entries left in the sync queue for entry check events
|
|
||||||
* @returns {Promise<Number>} promise resolving to the number of entries left
|
|
||||||
*/
|
|
||||||
//TODO maybe show this somewhere if efficient enough to run often
|
|
||||||
getSyncQueueLength: function () {
|
|
||||||
const wb = new Workbox('/service-worker.js');
|
|
||||||
wb.register();
|
|
||||||
return wb.messageSW({type: 'BGSYNC_COUNT_QUEUE'}).then((r) => {
|
|
||||||
console.log('sync que length :', r)
|
|
||||||
return r
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setFocus() {
|
|
||||||
this.$refs['amount_input_simple'].focus()
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* get ingredient from input string and create new shopping list entry using it
|
* get ingredient from input string and create new shopping list entry using it
|
||||||
*/
|
*/
|
||||||
|
@ -303,6 +303,7 @@
|
|||||||
"ShowRecentlyCompleted": "Show recently completed items",
|
"ShowRecentlyCompleted": "Show recently completed items",
|
||||||
"Completed": "Completed",
|
"Completed": "Completed",
|
||||||
"OfflineAlert": "You are offline, shopping list may not syncronize.",
|
"OfflineAlert": "You are offline, shopping list may not syncronize.",
|
||||||
|
"ShoppingBackgroundSyncWarning": "Due to bad connectivity some changes could not be synchronized. Automatically retrying when connection becomes available.",
|
||||||
"shopping_share": "Share Shopping List",
|
"shopping_share": "Share Shopping List",
|
||||||
"shopping_auto_sync": "Autosync",
|
"shopping_auto_sync": "Autosync",
|
||||||
"one_url_per_line": "One URL per line",
|
"one_url_per_line": "One URL per line",
|
||||||
|
@ -18,10 +18,10 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
supermarket_categories: [],
|
supermarket_categories: [],
|
||||||
supermarkets: [],
|
supermarkets: [],
|
||||||
|
|
||||||
total_unchecked:0,
|
total_unchecked: 0,
|
||||||
total_checked:0,
|
total_checked: 0,
|
||||||
total_unchecked_food:0,
|
total_unchecked_food: 0,
|
||||||
total_checked_food:0,
|
total_checked_food: 0,
|
||||||
|
|
||||||
// internal
|
// internal
|
||||||
currently_updating: false,
|
currently_updating: false,
|
||||||
@ -29,6 +29,9 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
autosync_has_focus: true,
|
autosync_has_focus: true,
|
||||||
undo_stack: [],
|
undo_stack: [],
|
||||||
|
|
||||||
|
queue_timeout_id: undefined,
|
||||||
|
item_check_sync_queue: {},
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
GROUP_CATEGORY: 'food.supermarket_category.name',
|
GROUP_CATEGORY: 'food.supermarket_category.name',
|
||||||
GROUP_CREATED_BY: 'created_by.display_name',
|
GROUP_CREATED_BY: 'created_by.display_name',
|
||||||
@ -135,6 +138,17 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
'translatable_label': 'Recipe'
|
'translatable_label': 'Recipe'
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* checks if failed items are contained in the sync queue
|
||||||
|
*/
|
||||||
|
has_failed_items: function () {
|
||||||
|
for (let i in this.item_check_sync_queue) {
|
||||||
|
if (this.item_check_sync_queue[i]['status'] === 'syncing_failed_before' || this.item_check_sync_queue[i]['status'] === 'waiting_failed_before') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
@ -315,12 +329,64 @@ export const useShoppingListStore = defineStore(_STORE_ID, {
|
|||||||
entry_id_list.push(i)
|
entry_id_list.push(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
let apiClient = new ApiApiFactory()
|
this.item_check_sync_queue
|
||||||
apiClient.bulkShoppingListEntry({'ids': entry_id_list, 'checked': checked}).then((r) => {
|
Vue.set(this.item_check_sync_queue, Math.random(), {
|
||||||
|
'ids': entry_id_list,
|
||||||
}).catch((err) => {
|
'checked': checked,
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
'status': 'waiting'
|
||||||
})
|
})
|
||||||
|
this.runSyncQueue(5)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* go through the list of queued requests and try to run them
|
||||||
|
* add request back to queue if it fails due to offline or timeout
|
||||||
|
* Do NOT call this method directly, always call using runSyncQueue method to prevent simultaneous runs
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_replaySyncQueue() {
|
||||||
|
if (navigator.onLine || document.location.href.includes('localhost')) {
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
let promises = []
|
||||||
|
//TODO merge entries with same checked state before updating?
|
||||||
|
|
||||||
|
for (let i in this.item_check_sync_queue) {
|
||||||
|
let entry = this.item_check_sync_queue[i]
|
||||||
|
Vue.set(entry, 'status', ((entry['status'] === 'waiting') ? 'syncing' : 'syncing_failed_before'))
|
||||||
|
Vue.set(this.item_check_sync_queue, i, entry)
|
||||||
|
|
||||||
|
let p = apiClient.bulkShoppingListEntry(entry, {timeout: 15000}).then((r) => {
|
||||||
|
Vue.delete(this.item_check_sync_queue, i)
|
||||||
|
}).catch((err) => {
|
||||||
|
if (err.code === "ERR_NETWORK" || err.code === "ECONNABORTED") {
|
||||||
|
Vue.set(entry, 'status', 'waiting_failed_before')
|
||||||
|
Vue.set(this.item_check_sync_queue, i, entry)
|
||||||
|
} else {
|
||||||
|
Vue.delete(this.item_check_sync_queue, i)
|
||||||
|
console.error('Failed API call for entry ', entry)
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
promises.push(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.allSettled(promises).finally(r => {
|
||||||
|
this.runSyncQueue(500)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.runSyncQueue(5000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* manages running the replaySyncQueue function after the given timeout
|
||||||
|
* calling this function might cancel a previously created timeout
|
||||||
|
* @param timeout time in ms after which to run the replaySyncQueue function
|
||||||
|
*/
|
||||||
|
runSyncQueue(timeout) {
|
||||||
|
clearTimeout(this.queue_timeout_id)
|
||||||
|
|
||||||
|
this.queue_timeout_id = setTimeout(() => {
|
||||||
|
this._replaySyncQueue()
|
||||||
|
}, timeout)
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* function to handle user "delaying" and "undelaying" shopping entries
|
* function to handle user "delaying" and "undelaying" shopping entries
|
||||||
|
Loading…
Reference in New Issue
Block a user