Skip to content

Commit

Permalink
Unify mod list update related functionality from Vue components to Vuex
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
anttimaki committed Dec 20, 2024
1 parent 74cfb43 commit aeef1bd
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 89 deletions.
37 changes: 6 additions & 31 deletions src/components/ModListUpdateBanner.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator';
import { Component, Vue } from 'vue-property-decorator';
import ManagerInformation from '../_managerinf/ManagerInformation';
@Component({})
export default class ModListUpdateBanner extends Vue {
updateError = '';
get appName(): string {
return ManagerInformation.APP_NAME;
}
Expand All @@ -19,35 +17,12 @@ export default class ModListUpdateBanner extends Vue {
return this.$store.state.tsMods.isThunderstoreModListUpdateInProgress;
}
@Watch('isUpdateInProgress')
onLoadingProgressChange(newVal: boolean, oldVal: boolean) {
// React to background update as well as the manual update from the banner.
if (!oldVal && newVal) {
this.updateError = '';
}
get updateError(): string {
return this.$store.state.tsMods.thunderstoreModListUpdateError;
}
async updateModList() {
if (this.isUpdateInProgress) {
return;
}
this.$store.commit('tsMods/startThunderstoreModListUpdate');
this.updateError = '';
try {
// Invalidate hash to force a refresh. Otherwise a scenario where
// the latest index hash is already present in IndexedDB but loading
// the package list into Vuex store has failed would cause the banner
// to just disappear when fetchAndProcessPackageList skips the actual
// update but updates the timestamp of the hash.
await this.$store.dispatch('tsMods/updateIndexHash', 'invalidated');
await this.$store.dispatch('tsMods/fetchAndProcessPackageList');
} catch (e) {
this.updateError = `${e}`;
} finally {
this.$store.commit('tsMods/finishThunderstoreModListUpdate');
}
await this.$store.dispatch('tsMods/fetchAndProcessPackageList');
}
}
</script>
Expand All @@ -56,10 +31,10 @@ export default class ModListUpdateBanner extends Vue {
<div v-if="!isModListLoaded" id="mod-list-update-banner" class="margin-bottom">
<div class="notification is-warning margin-right">
<span v-if="isUpdateInProgress">
Updating mod list from Thunderstore...
{{ $store.state.tsMods.thunderstoreModListUpdateStatus }}
</span>
<span v-else-if="updateError">
Error updating the mod list: {{ updateError }}<br />
Error updating the mod list: {{ updateError }}.<br />
{{ appName }} will keep trying to update the mod list in the background.
</span>
<span v-else>
Expand Down
13 changes: 3 additions & 10 deletions src/components/mixins/UtilityMixin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
21 changes: 3 additions & 18 deletions src/components/settings-components/SettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -299,33 +299,18 @@ 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");
}
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',
Expand Down
103 changes: 73 additions & 30 deletions src/store/modules/TsModsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface State {
mods: ThunderstoreMod[];
modsLastUpdated?: Date;
thunderstoreModListUpdateError: string;
thunderstoreModListUpdateStatus: string;
}

type ProgressCallback = (progress: number) => void;
Expand Down Expand Up @@ -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: <GetterTree<State, RootState>>{
Expand Down Expand Up @@ -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;
Expand All @@ -148,45 +153,83 @@ 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);
}
},

actions: <ActionTree<State, RootState>>{
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<void> {
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');
}
},

Expand Down

0 comments on commit aeef1bd

Please sign in to comment.