Skip to content

Commit

Permalink
Simplify download logic by omitting callback
Browse files Browse the repository at this point in the history
  • Loading branch information
VilppeRiskidev committed Jan 17, 2025
1 parent 6c26748 commit 53c1621
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 107 deletions.
4 changes: 4 additions & 0 deletions src/components/mixins/DownloadMixin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export default class DownloadMixin extends Vue {
this.$store.commit("closeDownloadModModal");
}
setIsModProgressModalOpen(open: boolean): void {
this.$store.commit('download/setIsModProgressModalOpen', open);
}
get isOpen(): boolean {
return this.$store.state.modals.isDownloadModModalOpen;
}
Expand Down
103 changes: 61 additions & 42 deletions src/components/views/DownloadModModal.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<div id='downloadProgressModal' :class="['modal', {'is-active':$store.state.download.isModProgressModalOpen}]" v-if="$store.getters['download/currentDownload'] !== null">
<div class="modal-background" @click="$store.commit('download/setIsModProgressModalOpen', false);"></div>
<div class="modal-background" @click="setIsModProgressModalOpen(false);"></div>
<div class='modal-content'>
<div class='notification is-info'>
<h3 class='title'>Downloading {{$store.getters['download/currentDownload'].modName}}</h3>
Expand All @@ -13,7 +13,7 @@
/>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="$store.commit('download/setIsModProgressModalOpen', false);"></button>
<button class="modal-close is-large" aria-label="close" @click="setIsModProgressModalOpen(false);"></button>
</div>
<DownloadModVersionSelectModal @download-mod="downloadHandler" />
<UpdateAllInstalledModsModal />
Expand Down Expand Up @@ -60,31 +60,30 @@ import ProfileModList from '../../r2mm/mods/ProfileModList';
store: Store<any>
): Promise<void> {
return new Promise(async (resolve, reject) => {
const tsMod = combo.getMod();
const tsVersion = combo.getVersion();
const assignId = await store.dispatch(
'download/addDownload',
[`${tsMod.getName()} (${tsVersion.getVersionNumber().toString()})`]
[`${combo.getMod().getName()} (${combo.getVersion().getVersionNumber().toString()})`]
);
setTimeout(() => {
ThunderstoreDownloaderProvider.instance.download(profile.asImmutableProfile(), tsMod, tsVersion, ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
try {
if (status === StatusEnum.FAILURE) {
store.commit('download/updateDownload', {assignId, failed: true});
if (err !== null) {
DownloadMixin.addSolutionsToError(err);
return reject(err);
setTimeout(async () => {
try {
const downloadedMods = await ThunderstoreDownloaderProvider.instance.download(profile.asImmutableProfile(), combo, ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
try {
if (status === StatusEnum.FAILURE) {
store.commit('download/updateDownload', {assignId, failed: true});
if (err !== null) {
DownloadMixin.addSolutionsToError(err);
return reject(err);
}
} else if (status === StatusEnum.PENDING) {
store.commit('download/updateDownload', {assignId, progress, modName});
}
} else if (status === StatusEnum.PENDING) {
store.commit('download/updateDownload', {assignId, progress, modName});
} catch (e) {
return reject(e);
}
} catch (e) {
return reject(e);
}
}, async (downloadedMods: ThunderstoreCombo[]) => {
ProfileModList.requestLock(async () => {
});
await ProfileModList.requestLock(async () => {
for (const combo of downloadedMods) {
try {
await DownloadModModal.installModAfterDownload(profile, combo.getMod(), combo.getVersion());
Expand All @@ -103,7 +102,9 @@ import ProfileModList from '../../r2mm/mods/ProfileModList';
}
return resolve();
});
});
} catch (e) {
store.commit('error/handleError', R2Error.fromThrownValue(e));
}
}, 1);
});
}
Expand All @@ -116,30 +117,48 @@ import ProfileModList from '../../r2mm/mods/ProfileModList';
[`${tsMod.getName()} (${tsVersion.getVersionNumber().toString()})`]
);
this.$store.commit('download/setIsModProgressModalOpen', true);
setTimeout(() => {
ThunderstoreDownloaderProvider.instance.download(this.profile.asImmutableProfile(), tsMod, tsVersion, this.ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
try {
if (status === StatusEnum.FAILURE) {
this.$store.commit('download/setIsModProgressModalOpen', false);
this.$store.commit('download/updateDownload', {assignId, failed: true});
if (err !== null) {
DownloadMixin.addSolutionsToError(err);
throw err;
}
} else if (status === StatusEnum.PENDING) {
this.$store.commit('download/updateDownload', {assignId, progress, modName});
}
} catch (e) {
this.$store.commit('error/handleError', R2Error.fromThrownValue(e));
}
}, async (downloadedMods) => {
await this.downloadCompletedCallback(downloadedMods);
this.$store.commit('download/setIsModProgressModalOpen', false);
});
this.setIsModProgressModalOpen(true);
const tsCombo = new ThunderstoreCombo();
tsCombo.setMod(tsMod);
tsCombo.setVersion(tsVersion);
setTimeout(async () => {
let downloadedMods: ThunderstoreCombo[] = [];
try {
downloadedMods = await ThunderstoreDownloaderProvider.instance.download(
this.profile.asImmutableProfile(),
tsCombo,
this.ignoreCache,
(progress, modName, status, err) => { this.downloadProgressCallback(assignId, progress, modName, status, err); }
);
} catch (e) {
this.setIsModProgressModalOpen(false);
this.$store.commit('error/handleError', R2Error.fromThrownValue(e));
}
await this.downloadCompletedCallback(downloadedMods);
this.setIsModProgressModalOpen(false);
}, 1);
}
downloadProgressCallback(assignId: number, progress: number, modName: string, status: number, err: R2Error | null) {
try {
if (status === StatusEnum.FAILURE) {
this.setIsModProgressModalOpen(false);
this.$store.commit('download/updateDownload', {assignId, failed: true});
if (err !== null) {
DownloadMixin.addSolutionsToError(err);
throw err;
}
} else if (status === StatusEnum.PENDING) {
this.$store.commit('download/updateDownload', {assignId, progress, modName});
}
} catch (e) {
this.$store.commit('error/handleError', R2Error.fromThrownValue(e));
}
};
static async installModAfterDownload(profile: Profile, mod: ThunderstoreMod, version: ThunderstoreVersion): Promise<R2Error | void> {
return new Promise(async (resolve, reject) => {
const manifestMod: ManifestV2 = new ManifestV2().fromThunderstoreMod(mod, version);
Expand Down
1 change: 0 additions & 1 deletion src/components/views/DownloadModVersionSelectModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import ModalCard from "../ModalCard.vue";
import DownloadMixin from "../../components/mixins/DownloadMixin.vue";
import R2Error from "../../model/errors/R2Error";
import ManifestV2 from "../../model/ManifestV2";
import ThunderstoreMod from "../../model/ThunderstoreMod";
import ThunderstoreVersion from "../../model/ThunderstoreVersion";
import { MOD_LOADER_VARIANTS } from "../../r2mm/installing/profile_installers/ModLoaderVariantRecord";
import * as PackageDb from "../../r2mm/manager/PackageDexieStore";
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/UpdateAllInstalledModsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ export default class UpdateAllInstalledModsModal extends mixins(DownloadMixin)
modsWithUpdates.map(value => `${value.getMod().getName()} (${value.getVersion().getVersionNumber().toString()})`)
);
this.$store.commit('download/setIsModProgressModalOpen', true);
this.setIsModProgressModalOpen(true);
ThunderstoreDownloaderProvider.instance.downloadLatestOfAll(modsWithUpdates, this.ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
try {
if (status === StatusEnum.FAILURE) {
this.$store.commit('download/setIsModProgressModalOpen', false);
this.setIsModProgressModalOpen(false);
this.$store.commit('download/updateDownload', {assignId, failed: true});
if (err !== null) {
DownloadMixin.addSolutionsToError(err);
Expand All @@ -66,7 +66,7 @@ export default class UpdateAllInstalledModsModal extends mixins(DownloadMixin)
}
}, async (downloadedMods) => {
await this.downloadCompletedCallback(downloadedMods);
this.$store.commit('download/setIsModProgressModalOpen', false);
this.setIsModProgressModalOpen(false);
});
}
}
Expand Down
21 changes: 10 additions & 11 deletions src/providers/ror2/downloading/ThunderstoreDownloaderProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import ProviderUtils from '../../generic/ProviderUtils';
import ThunderstoreVersion from '../../../model/ThunderstoreVersion';
import ThunderstoreMod from '../../../model/ThunderstoreMod';
import ThunderstoreCombo from '../../../model/ThunderstoreCombo';
import R2Error from '../../../model/errors/R2Error';
import { ImmutableProfile } from '../../../model/Profile';
Expand Down Expand Up @@ -49,17 +48,17 @@ export default abstract class ThunderstoreDownloaderProvider {
/**
* A top-level method to download the latest version of a mod including its dependencies.
*
* @param profile The profile the mod is downloaded for (needed to prevent dependencies from updating existing mods).
* @param mod The mod to be downloaded.
* @param modVersion The version of the mod to download.
* @param ignoreCache Download mod even if it already exists in the cache.
* @param callback Callback to show the current state of the downloads.
* @param completedCallback Callback to perform final actions against. Only called if {@param callback} has not returned a failed status.
* @param profile The profile the mod is downloaded for (needed to prevent dependencies from updating existing mods).
* @param combo The combo to be downloaded.
* @param ignoreCache Download mod even if it already exists in the cache.
* @param totalProgressCallback Callback to show the combined state of all the downloads.
*/
public abstract download(profile: ImmutableProfile, mod: ThunderstoreMod, modVersion: ThunderstoreVersion,
ignoreCache: boolean,
callback: (progress: number, modName: string, status: number, err: R2Error | null) => void,
completedCallback: (modList: ThunderstoreCombo[]) => void): void;
public abstract download(
profile: ImmutableProfile,
combo: ThunderstoreCombo,
ignoreCache: boolean,
totalProgressCallback: (progress: number, modName: string, status: number, err: R2Error | null) => void
): Promise<ThunderstoreCombo[]>;

/**
* A top-level method to download exact versions of exported mods.
Expand Down
97 changes: 47 additions & 50 deletions src/r2mm/downloading/BetterThunderstoreDownloader.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import ManifestV2 from "src/model/ManifestV2";
import ThunderstoreVersion from '../../model/ThunderstoreVersion';
import ThunderstoreMod from '../../model/ThunderstoreMod';
import StatusEnum from '../../model/enums/StatusEnum';
import axios, { AxiosResponse } from 'axios';
import ManifestV2 from "../../model/ManifestV2";
import ThunderstoreCombo from '../../model/ThunderstoreCombo';
import ZipExtract from '../installing/ZipExtract';
import R2Error from '../../model/errors/R2Error';
Expand Down Expand Up @@ -83,71 +82,69 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
});
}

public async download(profile: ImmutableProfile, mod: ThunderstoreMod, modVersion: ThunderstoreVersion,
ignoreCache: boolean,
callback: (progress: number, modName: string, status: number, err: R2Error | null) => void,
completedCallback: (modList: ThunderstoreCombo[]) => void) {
public async download(
profile: ImmutableProfile,
combo: ThunderstoreCombo,
ignoreCache: boolean,
totalProgressCallback: (progress: number, modName: string, status: number, err: R2Error | null) => void
): Promise<ThunderstoreCombo[]> {

const modList = await ProfileModList.getModList(profile);
if (modList instanceof R2Error) {
return callback(0, mod.getName(), StatusEnum.FAILURE, modList);
totalProgressCallback(0, combo.getMod().getName(), StatusEnum.FAILURE, modList);
throw modList;
}

let dependencies: ThunderstoreCombo[] = [];
await this.buildDependencySet(modVersion, dependencies, DependencySetBuilderMode.USE_EXACT_VERSION);
this.sortDependencyOrder(dependencies);
const combo = new ThunderstoreCombo();
combo.setMod(mod);
combo.setVersion(modVersion);

dependencies = await this.determineDependencyVersions(dependencies, combo, modVersion, modList);
let downloadableDependencySize = this.calculateInitialDownloadSize(dependencies);
const dependencies = await this.getDependenciesWithCorrectVersions(combo, modList);
const allModsToDownload = [...dependencies, combo];

let downloadCount = 0;
await this.downloadAndSave(combo, ignoreCache, async (progress: number, status: number, err: R2Error | null) => {
const singleModProgressCallback = (progress: number, status: number, err: R2Error | null) => {
if (status === StatusEnum.FAILURE) {
callback(0, mod.getName(), status, err);
} else if (status === StatusEnum.PENDING) {
callback(this.generateProgressPercentage(progress, downloadCount, downloadableDependencySize + 1), mod.getName(), status, err);
throw err;
}

let totalProgress: number;
if (status === StatusEnum.PENDING) {
totalProgress = this.generateProgressPercentage(progress, downloadCount, allModsToDownload.length);
} else if (status === StatusEnum.SUCCESS) {
totalProgress = this.generateProgressPercentage(100, downloadCount, allModsToDownload.length);
downloadCount += 1;
// If no dependencies, end here.
if (dependencies.length === 0) {
callback(100, mod.getName(), StatusEnum.PENDING, err);
completedCallback([combo]);
return;
}
} else {
console.error(`Ignore unknown status code "${status}"`);
return;
}
totalProgressCallback(totalProgress, combo.getMod().getName(), status, err);
}

// If dependencies, queue and download.
await this.queueDownloadDependencies(dependencies.entries(), ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
if (status === StatusEnum.FAILURE) {
callback(0, modName, status, err);
} else if (status === StatusEnum.PENDING) {
callback(this.generateProgressPercentage(progress, downloadCount, downloadableDependencySize + 1), modName, status, err);
} else if (status === StatusEnum.SUCCESS) {
callback(this.generateProgressPercentage(progress, downloadCount, downloadableDependencySize + 1), modName, StatusEnum.PENDING, err);
downloadCount += 1;
if (downloadCount >= dependencies.length + 1) {
callback(100, modName, StatusEnum.PENDING, err);
completedCallback([...dependencies, combo]);
}
}
});
for (const combo of allModsToDownload) {
if (!ignoreCache && await this.isVersionAlreadyDownloaded(combo)) {
totalProgressCallback(100, combo.getMod().getName(), StatusEnum.SUCCESS, null);
continue;
}
})

try {
const response = await this._downloadCombo(combo, singleModProgressCallback);
await this._saveDownloadResponse(response, combo, singleModProgressCallback);
} catch(e) {
throw R2Error.fromThrownValue(e, `Failed to download mod ${combo.getVersion().getFullName()}`);
}
}
return allModsToDownload;
}

// If combo is a modpack, use the modpack's dependency versions. If it isn't, get the latest versions.
public async determineDependencyVersions(dependencies: ThunderstoreCombo[], combo: ThunderstoreCombo, modVersion: ThunderstoreVersion, modList: ManifestV2[]) {
let isModpack = combo.getMod().getCategories().find(value => value === "Modpacks") !== undefined;
if (isModpack) {
return dependencies;
}
dependencies = [];
await this.buildDependencySet(modVersion, dependencies, DependencySetBuilderMode.USE_LATEST_VERSION);
private async getDependenciesWithCorrectVersions(combo: ThunderstoreCombo, modList: ManifestV2[]) {
const dependencies: ThunderstoreCombo[] = [];
const isModpack = combo.getMod().getCategories().some(value => value === "Modpacks");
const versionMode = isModpack ? DependencySetBuilderMode.USE_EXACT_VERSION : DependencySetBuilderMode.USE_LATEST_VERSION;

await this.buildDependencySet(combo.getVersion(), dependencies, versionMode);
this.sortDependencyOrder(dependencies);
// #270: Remove already-installed dependencies to prevent updating.
return dependencies.filter(dep => modList.find(installed => installed.getName() === dep.getMod().getFullName()) === undefined);
return dependencies.filter((dep) => {
return !(modList.some(installed => installed.getName() === dep.getMod().getFullName()));
});
}

public async downloadImportedMods(
Expand Down

0 comments on commit 53c1621

Please sign in to comment.