From 6d1a23d2ac17226bd7f7495303bbe21a696892cc Mon Sep 17 00:00:00 2001 From: Richard Steinmetz Date: Mon, 8 Jul 2024 10:51:41 +0200 Subject: [PATCH] feat: refresh calenders on remote changes Signed-off-by: Richard Steinmetz --- src/services/caldavService.js | 10 +++++++ src/store/calendarObjects.js | 10 +++---- src/store/calendars.js | 36 ++++++++++++++++++++++-- src/store/fetchedTimeRanges.js | 11 ++------ src/views/Calendar.vue | 51 +++++++++++++++++++++++++++++++++- 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/src/services/caldavService.js b/src/services/caldavService.js index 4aa275e2b..8796f56a9 100644 --- a/src/services/caldavService.js +++ b/src/services/caldavService.js @@ -77,6 +77,15 @@ const findAll = () => { return getCalendarHome().findAllCalDAVCollectionsGrouped() } +/** + * Fetch all calendars in the calendar home from the server + * + * @return {Promise} + */ +const findAllCalendars = () => { + return getCalendarHome().findAllCalendars() +} + /** * Fetch all subscriptions in the calendar home from the server */ @@ -252,6 +261,7 @@ export { initializeClientForUserView, initializeClientForPublicView, findAll, + findAllCalendars, findAllDeletedCalendars, findPublicCalendarsByTokens, findSchedulingInbox, diff --git a/src/store/calendarObjects.js b/src/store/calendarObjects.js index 3dc5dabb4..f17ce2e3c 100644 --- a/src/store/calendarObjects.js +++ b/src/store/calendarObjects.js @@ -296,17 +296,15 @@ export default defineStore('calendarObjects', { }, /** - * Adds an array of calendar-objects to the store + * Adds an array of calendar-objects to the store or update them if they already exist * * @param {object} data The destructuring object * @param {object[]} data.calendarObjects Calendar-objects to add */ - appendCalendarObjectsMutation({ calendarObjects = [] }) { + appendOrUpdateCalendarObjectsMutation({ calendarObjects = [] }) { for (const calendarObject of calendarObjects) { - if (!this.calendarObjects[calendarObject.id]) { - /// TODO this.calendarObjects[calendarObject.id] = calendarObject - Vue.set(this.calendarObjects, calendarObject.id, calendarObject) - } + /// TODO this.calendarObjects[calendarObject.id] = calendarObject + Vue.set(this.calendarObjects, calendarObject.id, calendarObject) } }, diff --git a/src/store/calendars.js b/src/store/calendars.js index 57ae8fc97..2027ad235 100644 --- a/src/store/calendars.js +++ b/src/store/calendars.js @@ -44,6 +44,7 @@ export default defineStore('calendars', { calendarsById: {}, initialCalendarsLoaded: false, editCalendarModal: undefined, + syncTokens: new Map(), } }, getters: { @@ -209,6 +210,21 @@ export default defineStore('calendars', { }) } }, + + /** + * Get the current sync token of a calendar or undefined it the calendar is not present + * + * @param {object} state The pinia state object + * @return {function({id: string}): string | undefined} + */ + getCalendarSyncToken: (state) => (calendar) => { + const existingCalendar = state.calendarsById[calendar.id] + if (!existingCalendar) { + return undefined + } + + return state.syncTokens.get(calendar.id) ?? existingCalendar.dav.syncToken + }, }, actions: { /** @@ -359,6 +375,7 @@ export default defineStore('calendars', { this.calendars.splice(this.calendars.indexOf(calendar), 1) /// TODO this.calendarsById.delete(calendar.id) Vue.delete(this.calendarsById, calendar.id) + this.syncTokens.delete(calendar.id) }, /** @@ -650,7 +667,7 @@ export default defineStore('calendars', { } } - calendarObjectsStore.appendCalendarObjectsMutation({ calendarObjects }) + calendarObjectsStore.appendOrUpdateCalendarObjectsMutation({ calendarObjects }) for (const calendarObjectId of calendarObjectIds) { if (this.calendarsById[calendar.id].calendarObjects.indexOf(calendarObjectId) === -1) { this.calendarsById[calendar.id].calendarObjects.push(calendarObjectId) @@ -858,7 +875,7 @@ export default defineStore('calendars', { const index = this.calendarsById[calendar.id]?.fetchedTimeRanges.indexOf(fetchedTimeRangeId) if (index !== -1) { - this.calendarsById[calendar.id].fetchedTimeRanges.slice(index, 1) + this.calendarsById[calendar.id].fetchedTimeRanges.splice(index, 1) } }, @@ -889,5 +906,20 @@ export default defineStore('calendars', { this.calendarsById[calendar.id].calendarObjects.slice(index, 1) } }, + + /** + * Update the sync token of a given calendar locally + * + * @param {object} data destructuring object + * @param {{id: string}} data.calendar Calendar from the store + * @param {string} data.syncToken New sync token value + */ + updateCalendarSyncToken({ calendar, syncToken }) { + if (!this.getCalendarById(calendar.id)) { + return + } + + this.syncTokens.set(calendar.id, syncToken) + }, }, }) diff --git a/src/store/fetchedTimeRanges.js b/src/store/fetchedTimeRanges.js index 6f3da6086..5f2f6ca31 100644 --- a/src/store/fetchedTimeRanges.js +++ b/src/store/fetchedTimeRanges.js @@ -94,14 +94,9 @@ export default defineStore('fetchedTimeRanges', { * @param {number} data.timeRangeId Id of time-range to remove */ removeTimeRange({ timeRangeId }) { - const obj = this.fetchedTimeRangesById[timeRangeId] - const index = this.fetchedTimeRanges.indexOf(obj) - - if (index !== -1) { - this.fetchedTimeRanges.splice(index, 1) - /// TODO this.fetchedTimeRangesById.splice(timeRangeId, 1) - Vue.delete(this.fetchedTimeRangesById, timeRangeId) - } + Vue.delete(this.fetchedTimeRangesById, timeRangeId) + this.fetchedTimeRanges = this.fetchedTimeRanges + .filter((timeRange) => timeRange.id !== timeRangeId) }, /** diff --git a/src/views/Calendar.vue b/src/views/Calendar.vue index b6e0f3253..c06e9c99d 100644 --- a/src/views/Calendar.vue +++ b/src/views/Calendar.vue @@ -76,6 +76,7 @@ import EditSimple from './EditSimple.vue' // Import CalDAV related methods import { + findAllCalendars, initializeClientForPublicView, initializeClientForUserView, } from '../services/caldavService.js' @@ -99,10 +100,12 @@ import Trashbin from '../components/AppNavigation/CalendarList/Trashbin.vue' import AppointmentConfigList from '../components/AppNavigation/AppointmentConfigList.vue' import useFetchedTimeRangesStore from '../store/fetchedTimeRanges.js' import useCalendarsStore from '../store/calendars.js' +import useCalendarObjectsStore from '../store/calendarObjects.js' import usePrincipalsStore from '../store/principals.js' import useSettingsStore from '../store/settings.js' import useWidgetStore from '../store/widget.js' import { mapStores, mapState } from 'pinia' +import { mapDavCollectionToCalendar } from '../models/calendar.js' export default { name: 'Calendar', @@ -148,12 +151,20 @@ export default { data() { return { loadingCalendars: true, + backgroundSyncJob: null, timeFrameCacheExpiryJob: null, showEmptyCalendarScreen: false, } }, computed: { - ...mapStores(useFetchedTimeRangesStore, useCalendarsStore, usePrincipalsStore, useSettingsStore, useWidgetStore), + ...mapStores( + useFetchedTimeRangesStore, + useCalendarsStore, + useCalendarObjectsStore, + usePrincipalsStore, + useSettingsStore, + useWidgetStore, + ), ...mapState(useSettingsStore, { timezoneId: 'getResolvedTimezone', }), @@ -208,6 +219,44 @@ export default { }, }, created() { + this.backgroundSyncJob = setInterval(async () => { + const currentUserPrincipal = this.principalsStore.getCurrentUserPrincipal + const calendars = (await findAllCalendars()) + .map((calendar) => mapDavCollectionToCalendar(calendar, currentUserPrincipal)) + for (const calendar of calendars) { + const existingSyncToken = this.calendarsStore.getCalendarSyncToken(calendar) + if (!existingSyncToken && !this.calendarsStore.getCalendarById(calendar.id)) { + // New calendar! + logger.debug(`Adding new calendar ${calendar.url}`) + this.calendarsStore.addCalendarMutation({ calendar }) + continue + } + + if (calendar.dav.syncToken === existingSyncToken) { + continue + } + + logger.debug(`Refetching calendar ${calendar.url} (syncToken changed)`) + const fetchedTimeRanges = this.fetchedTimeRangesStore + .getAllTimeRangesForCalendar(calendar.id) + for (const timeRange of fetchedTimeRanges) { + this.fetchedTimeRangesStore.removeTimeRange({ + timeRangeId: timeRange.id, + }) + this.calendarsStore.deleteFetchedTimeRangeFromCalendarMutation({ + calendar, + fetchedTimeRangeId: timeRange.id, + }) + } + + this.calendarsStore.updateCalendarSyncToken({ + calendar, + syncToken: calendar.dav.syncToken, + }) + this.calendarObjectsStore.modificationCount++ + } + }, 1000 * 30) + this.timeFrameCacheExpiryJob = setInterval(() => { const timestamp = (getUnixTimestampFromDate(dateFactory()) - 60 * 10) const timeRanges = this.fetchedTimeRangesStore.getAllTimeRangesOlderThan(timestamp)