Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor download provider's downloadImportedMods to work with less callbacks #1478

Open
wants to merge 2 commits into
base: importprofilemodal-refactor-pt517
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 25 additions & 35 deletions src/components/profiles-modals/ImportProfileModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { mixins } from "vue-class-component";
import { Component } from 'vue-property-decorator';

import ManagerInformation from "../../_managerinf/ManagerInformation";
import StatusEnum from "../../model/enums/StatusEnum";
import R2Error from "../../model/errors/R2Error";
import ExportFormat from "../../model/exports/ExportFormat";
import ExportMod from "../../model/exports/ExportMod";
Expand Down Expand Up @@ -200,24 +199,33 @@ export default class ImportProfileModal extends mixins(ProfilesMixin) {
await this.$store.dispatch('profiles/setSelectedProfile', { profileName: profileName, prewarmCache: true });
}

await this.downloadImportedProfileMods(mods, async () => {
try {
const comboList = await this.downloadImportedProfileMods(mods);
await this.downloadCompletedCallback(comboList, mods);
await ProfileUtils.extractZippedProfileFile(zipPath, profileName);
} catch (e) {
this.closeModal();
this.$store.commit('error/handleError', R2Error.fromThrownValue(e));
return;
}

if (this.importUpdateSelection === 'UPDATE') {
this.activeProfileName = targetProfile;
try {
await FileUtils.recursiveRemoveDirectoryIfExists(path.join(Profile.getRootDir(), targetProfile));
} catch (e) {
console.log("Failed to remove directory:", e);
}
await fs.rename(path.join(Profile.getRootDir(), profileName), path.join(Profile.getRootDir(), targetProfile));
if (this.importUpdateSelection === 'UPDATE') {
this.activeProfileName = targetProfile;
try {
await FileUtils.recursiveRemoveDirectoryIfExists(path.join(Profile.getRootDir(), targetProfile));
} catch (e) {
this.closeModal();
this.$store.commit('error/handleError', R2Error.fromThrownValue(e));
return;
}
await this.$store.dispatch('profiles/setSelectedProfile', { profileName: targetProfile, prewarmCache: true });
this.closeModal();
});
await fs.rename(path.join(Profile.getRootDir(), profileName), path.join(Profile.getRootDir(), targetProfile));
}

await this.$store.dispatch('profiles/setSelectedProfile', { profileName: targetProfile, prewarmCache: true });
this.closeModal();
}

async downloadImportedProfileMods(modList: ExportMod[], callback?: () => void) {
async downloadImportedProfileMods(modList: ExportMod[]): Promise<ThunderstoreCombo[]> {
const settings = this.$store.getters['settings'];
const ignoreCache = settings.getContext().global.ignoreCache;
const allMods = await PackageDb.getPackagesByNames(
Expand All @@ -226,31 +234,16 @@ export default class ImportProfileModal extends mixins(ProfilesMixin) {
);

this.percentageImported = 0;
ThunderstoreDownloaderProvider.instance.downloadImportedMods(
return await ThunderstoreDownloaderProvider.instance.downloadImportedMods(
modList,
allMods,
ignoreCache,
this.downloadProgressCallback,
async (comboList: ThunderstoreCombo[]) => {
await this.downloadCompletedCallback(comboList, modList, callback);
}
(progress: number) => this.percentageImported = Math.floor(progress)
);
}

// Called from the downloadImportedProfileMods function
downloadProgressCallback(progress: number, modName: string, status: number, err: R2Error | null) {
if (status == StatusEnum.FAILURE) {
this.closeModal();
if (err instanceof R2Error) {
this.$store.commit('error/handleError', err);
}
} else if (status == StatusEnum.PENDING) {
this.percentageImported = Math.floor(progress);
}
}

// Called from the downloadImportedProfileMods function
async downloadCompletedCallback(comboList: ThunderstoreCombo[], modList: ExportMod[], callback?: () => void) {
async downloadCompletedCallback(comboList: ThunderstoreCombo[], modList: ExportMod[]) {
let keepIterating = true;
for (const comboMod of comboList) {
if (!keepIterating) {
Expand Down Expand Up @@ -278,9 +271,6 @@ export default class ImportProfileModal extends mixins(ProfilesMixin) {
}
}
}
if (callback !== undefined) {
callback();
}
}

// Called when the name for the imported profile is given and confirmed by the user.
Expand Down
18 changes: 9 additions & 9 deletions src/providers/ror2/downloading/ThunderstoreDownloaderProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ThunderstoreMod from '../../../model/ThunderstoreMod';
import ThunderstoreCombo from '../../../model/ThunderstoreCombo';
import R2Error from '../../../model/errors/R2Error';
import ExportMod from '../../../model/exports/ExportMod';
import Profile, { ImmutableProfile } from '../../../model/Profile';
import { ImmutableProfile } from '../../../model/Profile';

export default abstract class ThunderstoreDownloaderProvider {

Expand Down Expand Up @@ -74,15 +74,15 @@ export default abstract class ThunderstoreDownloaderProvider {
/**
* A top-level method to download exact versions of exported mods.
*
* @param modList An array of {@class ExportMod} mods to download.
* @param allMods An array of all mods available from the Thunderstore API.
* @param ignoreCache Download mod even if it already exists in the cache.
* @param callback See {@method download}.
* @param completedCallback See {@method download}
* @param modList An array of {@class ExportMod} mods to download.
* @param allMods An array of all mods available from the Thunderstore API.
* @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 downloadImportedMods(modList: ExportMod[], allMods: ThunderstoreMod[], ignoreCache: boolean,
callback: (progress: number, modName: string, status: number, err: R2Error | null) => void,
completedCallback: (mods: ThunderstoreCombo[]) => void): void;
public abstract downloadImportedMods(
modList: ExportMod[], allMods: ThunderstoreMod[], ignoreCache: boolean,
totalProgressCallback: (progress: number) => void,
): Promise<ThunderstoreCombo[]>;

/**
* Generate the current progress across all downloads.
Expand Down
93 changes: 61 additions & 32 deletions src/r2mm/downloading/BetterThunderstoreDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ThunderstoreVersion from '../../model/ThunderstoreVersion';
import ThunderstoreMod from '../../model/ThunderstoreMod';
import VersionNumber from '../../model/VersionNumber';
import StatusEnum from '../../model/enums/StatusEnum';
import axios from 'axios';
import axios, { AxiosResponse } from 'axios';
import ThunderstoreCombo from '../../model/ThunderstoreCombo';
import ZipExtract from '../installing/ZipExtract';
import R2Error from '../../model/errors/R2Error';
Expand Down Expand Up @@ -181,10 +181,10 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
})
}

public async downloadImportedMods(modList: ExportMod[], allMods: ThunderstoreMod[], ignoreCache: boolean,
callback: (progress: number, modName: string, status: number, err: R2Error | null) => void,
completedCallback: (mods: ThunderstoreCombo[]) => void) {

public async downloadImportedMods(
modList: ExportMod[], allMods: ThunderstoreMod[], ignoreCache: boolean,
totalProgressCallback: (progress: number) => void
) {
const comboList = allMods.map((mod) => {
const targetMod = modList.find((importMod) => mod.getFullName() == importMod.getName());
const version = targetMod
Expand All @@ -200,30 +200,51 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
}).filter((combo): combo is ThunderstoreCombo => combo !== undefined);

if (comboList.length === 0) {
const err = new R2Error(
throw new R2Error(
'No importable mods found',
'None of the mods or versions listed in the shared profile are available on Thunderstore.',
'Make sure the shared profile is meant for the currently selected game.'
);
callback(0, '', StatusEnum.FAILURE, err);
return;
}

let downloadCount = 0;
await this.queueDownloadDependencies(comboList.entries(), ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {

// Mark the mod 80% processed when the download completes, save the remaining 20% for extracting.
const singleModProgressCallback = (progress: number, status: number, err: R2Error | null) => {
if (status === StatusEnum.FAILURE) {
callback(0, modName, status, err);
} else if (status === StatusEnum.PENDING) {
callback(this.generateProgressPercentage(progress, downloadCount, comboList.length + 1), modName, status, err);
throw err;
}

let totalProgress: number;
if (status === StatusEnum.PENDING) {
totalProgress = this.generateProgressPercentage(progress * 0.8, downloadCount, comboList.length);
} else if (status === StatusEnum.SUCCESS) {
callback(this.generateProgressPercentage(progress, downloadCount, comboList.length + 1), modName, StatusEnum.PENDING, err);
totalProgress = this.generateProgressPercentage(100, downloadCount, comboList.length);
downloadCount += 1;
if (downloadCount >= comboList.length) {
callback(100, modName, StatusEnum.PENDING, err);
completedCallback(comboList);
}
} else {
console.error(`Ignore unknown status code "${status}"`);
return;
}
});

totalProgressCallback(totalProgress);
}

for (const combo of comboList) {
if (!ignoreCache && await this.isVersionAlreadyDownloaded(combo)) {
singleModProgressCallback(100, 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()}`);
}
}

totalProgressCallback(100);
return comboList;
}

public generateProgressPercentage(progress: number, currentIndex: number, total: number): number {
Expand Down Expand Up @@ -256,7 +277,15 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
callback(100, StatusEnum.SUCCESS, null);
return;
}
axios.get(combo.getVersion().getDownloadUrl(), {
this._downloadCombo(combo, callback)
.then(async response => this._saveDownloadResponse(response, combo, callback))
.catch((reason: Error) => {
callback(100, StatusEnum.FAILURE, new R2Error(`Failed to download mod ${combo.getVersion().getFullName()}`, reason.message, null));
})
}

private async _downloadCombo(combo: ThunderstoreCombo, callback: (progress: number, status: number, err: R2Error | null) => void): Promise<AxiosResponse> {
return axios.get(combo.getVersion().getDownloadUrl(), {
onDownloadProgress: progress => {
callback((progress.loaded / progress.total) * 100, StatusEnum.PENDING, null);
},
Expand All @@ -265,19 +294,19 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
'Content-Type': 'application/zip',
'Access-Control-Allow-Origin': '*'
}
}).then(async response => {
const buf: Buffer = Buffer.from(response.data)
callback(100, StatusEnum.PENDING, null);
await this.saveToFile(buf, combo, (success: boolean, error?: R2Error) => {
if (success) {
callback(100, StatusEnum.SUCCESS, error || null);
} else {
callback(100, StatusEnum.FAILURE, error || null);
}
});
}).catch((reason: Error) => {
callback(100, StatusEnum.FAILURE, new R2Error(`Failed to download mod ${combo.getVersion().getFullName()}`, reason.message, null));
})
});
}

private async _saveDownloadResponse(response: AxiosResponse, combo: ThunderstoreCombo, callback: (progress: number, status: number, err: R2Error | null) => void): Promise<void> {
const buf: Buffer = Buffer.from(response.data)
callback(100, StatusEnum.PENDING, null);
await this.saveToFile(buf, combo, (success: boolean, error?: R2Error) => {
if (success) {
callback(100, StatusEnum.SUCCESS, error || null);
} else {
callback(100, StatusEnum.FAILURE, error || null);
}
});
}

public async saveToFile(response: Buffer, combo: ThunderstoreCombo, callback: (success: boolean, error?: R2Error) => void) {
Expand Down
Loading