From 349edb64a044fdf1df3162b98500c45e22261e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20M=C3=A4ki?= Date: Wed, 18 Dec 2024 17:23:09 +0200 Subject: [PATCH] Unify mod list update related functionality from Vue components to Vuex The Vuex method now handles preventing multiple simultaneous update calls. It also provides a simple status message for the update process to be shown in the UI when the update process is triggered by the user. --- src/components/ModListUpdateBanner.vue | 37 +------ src/components/mixins/UtilityMixin.vue | 13 +-- .../settings-components/SettingsView.vue | 21 +--- src/store/modules/TsModsModule.ts | 103 +++++++++++++----- 4 files changed, 85 insertions(+), 89 deletions(-) diff --git a/src/components/ModListUpdateBanner.vue b/src/components/ModListUpdateBanner.vue index 94d98c86..6c8ecbaf 100644 --- a/src/components/ModListUpdateBanner.vue +++ b/src/components/ModListUpdateBanner.vue @@ -1,12 +1,10 @@ @@ -56,10 +31,10 @@ export default class ModListUpdateBanner extends Vue {
- Updating mod list from Thunderstore... + {{ $store.state.tsMods.thunderstoreModListUpdateStatus }} - Error updating the mod list: {{ updateError }}
+ Error updating the mod list: {{ updateError }}.
{{ appName }} will keep trying to update the mod list in the background.
diff --git a/src/components/mixins/UtilityMixin.vue b/src/components/mixins/UtilityMixin.vue index cb8b82a4..f20bd716 100644 --- a/src/components/mixins/UtilityMixin.vue +++ b/src/components/mixins/UtilityMixin.vue @@ -32,23 +32,16 @@ export default class UtilityMixin extends Vue { return; } - if (this.$store.state.tsMods.isThunderstoreModListUpdateInProgress) { - return; - } + await this.$store.dispatch("tsMods/fetchAndProcessPackageList"); - try { - this.$store.commit("tsMods/startThunderstoreModListUpdate"); - await this.$store.dispatch('tsMods/fetchAndProcessPackageList'); - } catch (e) { + if (this.$store.state.tsMods.thunderstoreModListUpdateError.length > 0) { if (this.tsBackgroundRefreshFailed) { console.error("Two consecutive background refresh attempts failed"); - throw e; + throw new Error(this.$store.state.tsMods.thunderstoreModListUpdateError); } this.tsBackgroundRefreshFailed = true; return; - } finally { - this.$store.commit("tsMods/finishThunderstoreModListUpdate"); } this.tsBackgroundRefreshFailed = false; diff --git a/src/components/settings-components/SettingsView.vue b/src/components/settings-components/SettingsView.vue index 93390623..b34b1721 100644 --- a/src/components/settings-components/SettingsView.vue +++ b/src/components/settings-components/SettingsView.vue @@ -299,10 +299,10 @@ import CdnProvider from '../../providers/generic/connection/CdnProvider'; 'Check for any new mod releases.', async () => { if (this.$store.state.tsMods.isThunderstoreModListUpdateInProgress) { - return "Checking for new releases"; + return this.$store.state.tsMods.thunderstoreModListUpdateStatus || "Updating..."; } if (this.$store.state.tsMods.thunderstoreModListUpdateError.length > 0) { - return "Error getting new mods: " + this.$store.state.tsMods.thunderstoreModListUpdateError; + return "Error updating the mod list: " + this.$store.state.tsMods.thunderstoreModListUpdateError; } if (this.$store.state.tsMods.modsLastUpdated !== undefined) { return "Cache date: " + moment(this.$store.state.tsMods.modsLastUpdated).format("MMMM Do YYYY, h:mm:ss a"); @@ -310,22 +310,7 @@ import CdnProvider from '../../providers/generic/connection/CdnProvider'; return "No API information available"; }, 'fa-exchange-alt', - async () => { - if (this.$store.state.tsMods.isThunderstoreModListUpdateInProgress) { - return; - } - - this.$store.commit("tsMods/startThunderstoreModListUpdate"); - this.$store.commit("tsMods/setThunderstoreModListUpdateError", ""); - - try { - await this.$store.dispatch("tsMods/fetchAndProcessPackageList"); - } catch (e) { - this.$store.commit("tsMods/setThunderstoreModListUpdateError", e); - } finally { - this.$store.commit("tsMods/finishThunderstoreModListUpdate"); - } - } + async () => await this.$store.dispatch("tsMods/fetchAndProcessPackageList") ), new SettingsRow( 'Other', diff --git a/src/store/modules/TsModsModule.ts b/src/store/modules/TsModsModule.ts index 9bb1ccc6..6b7fb35e 100644 --- a/src/store/modules/TsModsModule.ts +++ b/src/store/modules/TsModsModule.ts @@ -25,6 +25,7 @@ interface State { mods: ThunderstoreMod[]; modsLastUpdated?: Date; thunderstoreModListUpdateError: string; + thunderstoreModListUpdateStatus: string; } type ProgressCallback = (progress: number) => void; @@ -58,8 +59,10 @@ export const TsModsModule = { mods: [], /*** When was the mod list last refreshed from the API? */ modsLastUpdated: undefined, - /*** Error shown on UI after manual mod list refresh fails */ - thunderstoreModListUpdateError: '' + /*** Error shown on UI after mod list refresh fails */ + thunderstoreModListUpdateError: '', + /*** Status shown on UI during mod list refresh */ + thunderstoreModListUpdateStatus: '' }), getters: >{ @@ -124,12 +127,14 @@ export const TsModsModule = { state.mods = []; state.modsLastUpdated = undefined; state.thunderstoreModListUpdateError = ''; + state.thunderstoreModListUpdateStatus = ''; }, clearModCache(state) { state.cache.clear(); }, finishThunderstoreModListUpdate(state) { state.isThunderstoreModListUpdateInProgress = false; + state.thunderstoreModListUpdateStatus = ''; }, setMods(state, payload: ThunderstoreMod[]) { state.mods = payload; @@ -148,8 +153,12 @@ export const TsModsModule = { state.thunderstoreModListUpdateError = msg; } }, + setThunderstoreModListUpdateStatus(state, status: string) { + state.thunderstoreModListUpdateStatus = status; + }, startThunderstoreModListUpdate(state) { state.isThunderstoreModListUpdateInProgress = true; + state.thunderstoreModListUpdateError = ''; }, updateDeprecated(state, allMods: ThunderstoreMod[]) { state.deprecated = Deprecations.getDeprecatedPackageMap(allMods); @@ -157,36 +166,70 @@ export const TsModsModule = { }, actions: >{ - async fetchAndProcessPackageList({dispatch, state}) { - const packageListIndex: PackageListIndex = await dispatch('fetchPackageListIndex'); - - // If the package list is up to date, only update the timestamp. Otherwise, - // fetch the new one and store it into IndexedDB. - if (packageListIndex.isLatest) { - await dispatch('updateIndexHash', packageListIndex.hash); - } else { - const packageListChunks = await dispatch( - 'fetchPackageListChunks', - {chunkUrls: packageListIndex.content}, - ); - await dispatch( - 'updatePersistentCache', - {chunks: packageListChunks, indexHash: packageListIndex.hash}, - ); + /** + * Complete update process of the mod list, to be used after + * passing the splash screen. + */ + async fetchAndProcessPackageList({commit, dispatch, state}): Promise { + if (state.isThunderstoreModListUpdateInProgress) { + return; } - // If the package list was up to date and the mod list is already loaded to - // Vuex, just update the timestamp. Otherwise, load the list from IndexedDB - // to Vuex. This needs to be done even if the index hasn't updated when the - // mod list in Vuex is empty, as this indicates an error state and otherwise - // the user would be stuck with empty list until a new index hash is - // available via the API. - if (packageListIndex.isLatest && state.mods.length > 0) { - await dispatch('updateModsLastUpdated'); - } else { - await dispatch('updateMods'); - await dispatch('profile/tryLoadModListFromDisk', null, {root: true}); - await dispatch('prewarmCache'); + commit('startThunderstoreModListUpdate'); + + try { + commit('setThunderstoreModListUpdateStatus', 'Checking for mod list updates from Thunderstore...'); + let packageListIndex: PackageListIndex; + + try { + packageListIndex = await dispatch('fetchPackageListIndex'); + } catch { + // "Retry failed after 5 attempts" isn't very helpful error message. + throw new Error('Failed to check for updates from Thunderstore'); + } + + // If the package list is up to date, only update the timestamp. Otherwise, + // fetch the new one and store it into IndexedDB. + if (packageListIndex.isLatest) { + await dispatch('updateIndexHash', packageListIndex.hash); + } else { + const packageListChunks = await dispatch( + 'fetchPackageListChunks', + { + chunkUrls: packageListIndex.content, + progressCallback: (progress: number) => commit( + 'setThunderstoreModListUpdateStatus', + `Loading latest mod list from Thunderstore: ${progress}%` + ), + }, + ); + + commit('setThunderstoreModListUpdateStatus', 'Storing the mod list into local cache...'); + await dispatch( + 'updatePersistentCache', + {chunks: packageListChunks, indexHash: packageListIndex.hash}, + ); + } + + // If the package list was up to date and the mod list is already loaded to + // Vuex, just update the timestamp. Otherwise, load the list from IndexedDB + // to Vuex. This needs to be done even if the index hasn't updated when the + // mod list in Vuex is empty, as this indicates an error state and otherwise + // the user would be stuck with empty list until a new index hash is + // available via the API. + if (packageListIndex.isLatest && state.mods.length > 0) { + await dispatch('updateModsLastUpdated'); + } else { + commit('setThunderstoreModListUpdateStatus', 'Processing the mod list...'); + await dispatch('updateMods'); + commit('setThunderstoreModListUpdateStatus', 'Almost done...'); + await dispatch('profile/tryLoadModListFromDisk', null, {root: true}); + await dispatch('prewarmCache'); + } + } catch (e) { + commit('setThunderstoreModListUpdateError', e); + } finally { + commit('finishThunderstoreModListUpdate'); } },