Skip to content

Commit

Permalink
WIP: Callback stack refactoring and installation after download
Browse files Browse the repository at this point in the history
  • Loading branch information
VilppeRiskidev committed Nov 26, 2024
1 parent 92d1e8b commit 4f22676
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 86 deletions.
54 changes: 34 additions & 20 deletions src/components/views/DownloadModModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
:value='activeDownloadProgress'
:className="['is-dark']"
/>
<div v-if="activeInstallProgress > 0">
<h3 class='title'>Installing {{activeDownloadModName}}</h3>
<p>{{Math.floor(activeInstallProgress)}}% complete</p>
<Progress
:max='100'
:value='activeInstallProgress'
:className="['is-dark']"
/>
</div>
</div>
</div>
<button class="modal-close is-large" aria-label="close" @click="closeModal();"></button>
Expand Down Expand Up @@ -145,6 +154,10 @@ let assignId = 0;
return this.$store.getters['modDownload/activeDownloadModName'];
}
get activeInstallProgress(): number {
return this.$store.getters['modDownload/activeInstallProgress'];
}
get activeGame(): Game {
return this.$store.state.activeGame;
}
Expand Down Expand Up @@ -175,8 +188,8 @@ let assignId = 0;
failed: false,
};
DownloadModModal.allVersions.push([currentAssignId, progressObject]);
setTimeout(() => {
ThunderstoreDownloaderProvider.instance.download(profile.asImmutableProfile(), tsMod, tsVersion, ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
setTimeout(async () => {
const downloadedMods = await ThunderstoreDownloaderProvider.instance.download(profile.asImmutableProfile(), tsMod, tsVersion, ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
const assignIndex = DownloadModModal.allVersions.findIndex(([number, val]) => number === currentAssignId);
if (status === StatusEnum.FAILURE) {
if (err !== null) {
Expand All @@ -196,28 +209,29 @@ let assignId = 0;
}
DownloadModModal.allVersions[assignIndex] = [currentAssignId, obj];
}
}, async (downloadedMods: ThunderstoreCombo[]) => {
ProfileModList.requestLock(async () => {
for (const combo of downloadedMods) {
try {
await DownloadModModal.installModAfterDownload(profile, combo.getMod(), combo.getVersion());
} catch (e) {
return reject(
R2Error.fromThrownValue(e, `Failed to install mod [${combo.getMod().getFullName()}]`)
);
}
});
await ProfileModList.requestLock(async () => {
for (const combo of downloadedMods) {
try {
await DownloadModModal.installModAfterDownload(profile, combo.getMod(), combo.getVersion());
} catch (e) {
return reject(
R2Error.fromThrownValue(e, `Failed to install mod [${combo.getMod().getFullName()}]`)
);
}
const modList = await ProfileModList.getModList(profile.asImmutableProfile());
if (!(modList instanceof R2Error)) {
const err = await ConflictManagementProvider.instance.resolveConflicts(modList, profile);
if (err instanceof R2Error) {
return reject(err);
}
}
const modList = await ProfileModList.getModList(profile.asImmutableProfile());
if (!(modList instanceof R2Error)) {
const err = await ConflictManagementProvider.instance.resolveConflicts(modList, profile);
if (err instanceof R2Error) {
return reject(err);
}
return resolve();
});
}
return resolve();
});
}, 1);
});
}
Expand Down
14 changes: 7 additions & 7 deletions src/providers/ror2/downloading/ThunderstoreDownloaderProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ export default abstract class ThunderstoreDownloaderProvider {
* @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.
*/
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, mod: ThunderstoreMod, modVersion: ThunderstoreVersion,
ignoreCache: boolean,
callback: (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 Expand Up @@ -87,11 +87,11 @@ export default abstract class ThunderstoreDownloaderProvider {
* Iterate the {@class ThunderstoreCombo} array to perform the download for each mod.
* Progress to the next one recursively once the callback received has been successful.
*
* @param entries IterableIterator of entries for {@class ThunderstoreCombo} mods to download.
* @param entries The {@class ThunderstoreCombo} mods to download.
* @param ignoreCache Should mod be downloaded even if it already exists in the cache?
* @param callback See {@method download}
*/
public abstract queueDownloadDependencies(entries: IterableIterator<[number, ThunderstoreCombo]>, ignoreCache: boolean, callback: (progress: number, modName: string, status: number, err: R2Error | null) => void): void
public abstract queueDownloadDependencies(entries: ThunderstoreCombo[], ignoreCache: boolean, callback: (progress: number, modName: string, status: number, err: R2Error | null) => void): void

/**
* Generate the total count of mods to be downloaded. Cached mods are not included in this count unless download cache is disabled.
Expand Down
103 changes: 62 additions & 41 deletions src/r2mm/downloading/BetterThunderstoreDownloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
let downloadCount = 0;
const downloadableDependencySize = this.calculateInitialDownloadSize(dependencies);

await this.queueDownloadDependencies(dependencies.entries(), ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
await this.queueDownloadDependencies(dependencies, ignoreCache, (progress: number, modName: string, status: number, err: R2Error | null) => {
if (status === StatusEnum.FAILURE) {
callback(0, modName, status, err);
} else if (status === StatusEnum.PENDING) {
Expand All @@ -82,10 +82,12 @@ 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, mod: ThunderstoreMod, modVersion: ThunderstoreVersion,
ignoreCache: boolean,
callback: (progress: number, modName: string, status: number, err: R2Error | null) => void
): Promise<ThunderstoreCombo[]> {
let dependencies: ThunderstoreCombo[] = [];
await this.buildDependencySet(modVersion, dependencies, DependencySetBuilderMode.USE_EXACT_VERSION);
this.sortDependencyOrder(dependencies);
Expand All @@ -94,9 +96,11 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
combo.setVersion(modVersion);
let downloadCount = 0;

let downloadedModsList: ThunderstoreCombo[] = [combo];

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

let downloadableDependencySize = this.calculateInitialDownloadSize(dependencies);
Expand All @@ -122,30 +126,32 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
callback(this.generateProgressPercentage(progress, downloadCount, downloadableDependencySize + 1), mod.getName(), status, err);
} else if (status === StatusEnum.SUCCESS) {
downloadCount += 1;
// If no dependencies, end here.
if (dependencies.length === 0) {
callback(100, mod.getName(), StatusEnum.PENDING, err);
completedCallback([combo]);
return;
}
}
});

// 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]);
}
}
});
// If no dependencies, end here.
if (dependencies.length === 0) {
callback(100, mod.getName(), StatusEnum.SUCCESS, null);
return downloadedModsList;
}

// If dependencies, queue and download.
let downloadedDependencies = await this.queueDownloadDependencies(dependencies, 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);
}
}
})
});

downloadedModsList.push(...downloadedDependencies);
return downloadedModsList;
}

public async downloadImportedMods(
Expand Down Expand Up @@ -197,20 +203,35 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
return completedProgress + (progress * 1/total);
}

public async queueDownloadDependencies(entries: IterableIterator<[number, ThunderstoreCombo]>, ignoreCache: boolean, callback: (progress: number, modName: string, status: number, err: R2Error | null) => void) {
const entry = entries.next();
if (!entry.done) {
await this.downloadAndSave(entry.value[1] as ThunderstoreCombo, ignoreCache, async (progress: number, status: number, err: R2Error | null) => {
if (status === StatusEnum.FAILURE) {
callback(0, (entry.value[1] as ThunderstoreCombo).getMod().getName(), status, err);
} else if (status === StatusEnum.PENDING) {
callback(progress, (entry.value[1] as ThunderstoreCombo).getMod().getName(), status, err);
} else if (status === StatusEnum.SUCCESS) {
callback(100, (entry.value[1] as ThunderstoreCombo).getMod().getName(), status, err);
await this.queueDownloadDependencies(entries, ignoreCache, callback);
}
public async queueDownloadDependencies(
thunderstoreCombos: ThunderstoreCombo[],
ignoreCache: boolean,
callback: (progress: number, modName: string, status: number, err: R2Error | null) => void
): Promise<ThunderstoreCombo[]> {
const downloadTasks: Promise<void>[] = [];
const mods: ThunderstoreCombo[] = [];

thunderstoreCombos.forEach((mod) => {
const task = new Promise<void>((resolve, reject) => {
this.downloadAndSave(mod, ignoreCache, (progress: number, status: number, err: R2Error | null) => {
if (status === StatusEnum.FAILURE) {
callback(0, mod.getMod().getName(), status, err);
reject(err); // Reject the promise if there's a failure
} else if (status === StatusEnum.PENDING) {
callback(progress, mod.getMod().getName(), status, err);
} else if (status === StatusEnum.SUCCESS) {
callback(100, mod.getMod().getName(), status, err);
mods.push(mod);
resolve();
}
});
});
}

downloadTasks.push(task);
});

await Promise.all(downloadTasks);
return mods;
}

public calculateInitialDownloadSize(list: ThunderstoreCombo[]): number {
Expand Down
Loading

0 comments on commit 4f22676

Please sign in to comment.