Skip to content

Commit 0b4cd1c

Browse files
Calculate and display the download progress based on the file size
* Calculate the downloaded file size and the progress percentage based on that * Save the values to the local store in the callback * (Importing uses the callback values without storing them) * Display the the progress (percentage), current progress (file size) and total file size to be downloaded * Use the values in DownloadMonitor, DownloadProgressModal and ImportProfileModal
1 parent 73d76d1 commit 0b4cd1c

File tree

8 files changed

+98
-46
lines changed

8 files changed

+98
-46
lines changed

src/components/profiles-modals/ImportProfileModal.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useProfilesComposable } from '../composables/ProfilesComposable';
1515
import { computed, nextTick, ref, watch, watchEffect } from 'vue';
1616
import { getStore } from '../../providers/generic/store/StoreProvider';
1717
import { State } from '../../store';
18+
import FileUtils from "../../utils/FileUtils";
1819
1920
const VALID_PROFILE_CODE_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2021
@@ -250,15 +251,18 @@ async function onImportTargetSelected() {
250251
async function importProfile(targetProfileName: string, mods: ExportMod[], zipPath: string) {
251252
activeStep.value = 'PROFILE_IS_BEING_IMPORTED';
252253
importPhaseDescription.value = 'Downloading mods: 0%';
253-
const progressCallback = (progress: number|string) => typeof progress === "number"
254-
? importPhaseDescription.value = `Downloading mods: ${Math.floor(progress)}%`
255-
: importPhaseDescription.value = progress;
254+
const progressCallback = (_progress: number, downloadedSize: number, totalDownloadSize: number) => {
255+
importPhaseDescription.value = `Downloading mods: ${Math.floor(downloadedSize / totalDownloadSize * 100)}%` +
256+
` (${FileUtils.humanReadableSize(downloadedSize)} / ${FileUtils.humanReadableSize(totalDownloadSize)})`;
257+
};
256258
const isUpdate = importUpdateSelection.value === 'UPDATE';
257259
258260
try {
259261
const combos = profileMods.value.known as ThunderstoreCombo[];
260262
await store.dispatch('download/downloadToCache', {combos, progressCallback});
261-
await ProfileUtils.populateImportedProfile(combos, mods, targetProfileName, isUpdate, zipPath, progressCallback);
263+
await ProfileUtils.populateImportedProfile(combos, mods, targetProfileName, isUpdate, zipPath, (progress) => {
264+
importPhaseDescription.value = progress;
265+
});
262266
} catch (e) {
263267
await store.dispatch('profiles/ensureProfileExists');
264268
closeModal();

src/components/views/DownloadProgressModal.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Progress } from '../all';
33
import { getStore } from '../../providers/generic/store/StoreProvider';
44
import { State } from '../../store';
5+
import FileUtils from '../../utils/FileUtils';
56
67
const store = getStore<State>();
78
@@ -29,7 +30,10 @@ function closeModal() {
2930
Installing {{$store.getters['download/currentDownload'].modName}}
3031
</h3>
3132

32-
<p>Downloading: {{$store.getters['download/currentDownload'].downloadProgress}}% complete</p>
33+
<p>
34+
Downloading: {{$store.getters['download/currentDownload'].downloadProgress}}% complete
35+
({{FileUtils.humanReadableSize($store.getters['download/currentDownload'].downloadedSize)}} / {{FileUtils.humanReadableSize($store.getters['download/currentDownload'].totalDownloadSize)}})
36+
</p>
3337

3438
<Progress
3539
:max='100'

src/model/ThunderstoreVersion.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export default class ThunderstoreVersion {
1212
private enabled: boolean = true;
1313
private downloads: number = 0;
1414
private downloadUrl: string = '';
15+
private fileSize: number = 0;
1516

1617
public static parseFromThunderstoreData(data: any): ThunderstoreVersion {
1718
const version = new ThunderstoreVersion();
@@ -28,6 +29,7 @@ export default class ThunderstoreVersion {
2829
this.setIcon(version.icon);
2930
this.setDownloadCount(version.downloads);
3031
this.setDownloadUrl(version.download_url);
32+
this.setFileSize(version.file_size);
3133
return this;
3234
}
3335

@@ -106,4 +108,12 @@ export default class ThunderstoreVersion {
106108
public setDownloadUrl(url: string) {
107109
this.downloadUrl = url;
108110
}
111+
112+
public setFileSize(size: number) {
113+
this.fileSize = size;
114+
}
115+
116+
public getFileSize(): number {
117+
return this.fileSize;
118+
}
109119
}

src/pages/DownloadMonitor.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@
5454
<div class="col">
5555
<p v-if="downloadObject.status === DownloadStatusEnum.DOWNLOADING">Downloading: {{ downloadObject.modName }}</p>
5656
<p v-else>Downloading:</p>
57-
<p>{{Math.min(Math.floor(downloadObject.downloadProgress), 100)}}% complete</p>
57+
<p>{{`${Math.min(Math.floor(downloadObject.downloadProgress), 100)}% complete ` +
58+
`(${FileUtils.humanReadableSize(downloadObject.downloadedSize)} / ` +
59+
`${FileUtils.humanReadableSize(downloadObject.totalDownloadSize)})`}}</p>
5860
<Progress
5961
:max='100'
6062
:value='downloadObject.downloadProgress'
@@ -110,6 +112,7 @@
110112
111113
import { Hero } from '../components/all';
112114
import Progress from '../components/Progress.vue';
115+
import FileUtils from "../utils/FileUtils";
113116
import { DownloadStatusEnum } from '../model/enums/DownloadStatusEnum';
114117
</script>
115118

src/providers/ror2/downloading/ThunderstoreDownloaderProvider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ProviderUtils from '../../generic/ProviderUtils';
22
import ThunderstoreCombo from '../../../model/ThunderstoreCombo';
33
import R2Error from '../../../model/errors/R2Error';
4+
import ManagerSettings from '../../../r2mm/manager/ManagerSettings';
45

56
export default abstract class ThunderstoreDownloaderProvider {
67

@@ -19,7 +20,8 @@ export default abstract class ThunderstoreDownloaderProvider {
1920
public abstract download(
2021
combos: ThunderstoreCombo[],
2122
ignoreCache: boolean,
22-
totalProgressCallback: (progress: number, modName: string, status: number, err: R2Error | null) => void
23+
settings: ManagerSettings,
24+
totalProgressCallback: (progress: number, downloadedSize: number, totalDownloadSize: number, modName: string, status: number, err: R2Error | null) => void
2325
): Promise<void>;
2426

2527
/**
@@ -40,4 +42,5 @@ export default abstract class ThunderstoreDownloaderProvider {
4042
*/
4143
public abstract isVersionAlreadyDownloaded(combo: ThunderstoreCombo): Promise<boolean>;
4244

45+
public abstract getTotalDownloadSizeInBytes(combos: ThunderstoreCombo[], settings: ManagerSettings): Promise<number>;
4346
}

src/r2mm/downloading/BetterThunderstoreDownloader.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,42 +9,53 @@ import FsProvider from '../../providers/generic/file/FsProvider';
99
import FileWriteError from '../../model/errors/FileWriteError';
1010
import ThunderstoreDownloaderProvider from '../../providers/ror2/downloading/ThunderstoreDownloaderProvider';
1111
import ManagerInformation from '../../_managerinf/ManagerInformation';
12-
import { generateProgressPercentage } from '../../utils/DownloadUtils';
12+
import ManagerSettings from '../manager/ManagerSettings';
1313

1414
export default class BetterThunderstoreDownloader extends ThunderstoreDownloaderProvider {
1515

1616
public async download(
1717
combos: ThunderstoreCombo[],
1818
ignoreCache: boolean,
19-
totalProgressCallback: (progress: number, modName: string, status: number, err: R2Error | null) => void
19+
settings: ManagerSettings,
20+
totalProgressCallback: (progress: number, downloadedSize: number, totalDownloadSize: number, modName: string, status: number, err: R2Error | null) => void
2021
): Promise<void> {
2122
let modInProgressName = combos[0].getMod().getName();
2223
let downloadCount = 0;
24+
let finishedModsDownloadedSize = 0;
25+
let modInProgressSizeProgress = 0;
2326

24-
// Mark the mod 80% processed when the download completes, save the remaining 20% for extracting.
25-
const singleModProgressCallback = (downloadProgress: number, status: number, err: R2Error | null) => {
27+
const totalDownloadSize = await this.getTotalDownloadSizeInBytes(combos, settings);
28+
29+
const singleModProgressCallback = (downloadProgress: number, modInProgressSize: number, status: number, err: R2Error | null) => {
2630
if (status === StatusEnum.FAILURE) {
2731
throw err;
2832
}
2933

30-
let totalDownloadProgress: number;
3134
if (status === StatusEnum.PENDING) {
32-
totalDownloadProgress = generateProgressPercentage(downloadProgress * 0.8, downloadCount, combos.length);
35+
modInProgressSizeProgress = (downloadProgress / 100) * modInProgressSize;
3336
} else if (status === StatusEnum.SUCCESS) {
34-
totalDownloadProgress = generateProgressPercentage(100, downloadCount, combos.length);
37+
finishedModsDownloadedSize += modInProgressSize;
38+
modInProgressSizeProgress = 0;
3539
downloadCount += 1;
3640
} else {
3741
console.error(`Ignore unknown status code "${status}"`);
3842
return;
3943
}
40-
totalProgressCallback(Math.round(totalDownloadProgress), modInProgressName, status, err);
44+
totalProgressCallback(
45+
Math.round(finishedModsDownloadedSize / totalDownloadSize * 100),
46+
finishedModsDownloadedSize + modInProgressSizeProgress,
47+
totalDownloadSize,
48+
modInProgressName,
49+
status,
50+
err
51+
);
4152
}
4253

4354
for (const comboInProgress of combos) {
4455
modInProgressName = comboInProgress.getMod().getName();
4556

4657
if (!ignoreCache && await this.isVersionAlreadyDownloaded(comboInProgress)) {
47-
singleModProgressCallback(100, StatusEnum.SUCCESS, null);
58+
singleModProgressCallback(100, 0, StatusEnum.SUCCESS, null);
4859
continue;
4960
}
5061

@@ -57,10 +68,15 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
5768
}
5869
}
5970

60-
private async _downloadCombo(combo: ThunderstoreCombo, callback: (progress: number, status: number, err: R2Error | null) => void): Promise<AxiosResponse> {
71+
private async _downloadCombo(combo: ThunderstoreCombo, callback: (progress: number, downloadedSize: number, status: number, err: R2Error | null) => void): Promise<AxiosResponse> {
6172
return axios.get(combo.getVersion().getDownloadUrl(), {
6273
onDownloadProgress: progress => {
63-
callback((progress.loaded / progress.total) * 100, StatusEnum.PENDING, null);
74+
callback(
75+
(progress.loaded / progress.total) * 100,
76+
combo.getVersion().getFileSize(),
77+
StatusEnum.PENDING,
78+
null
79+
);
6480
},
6581
responseType: 'arraybuffer',
6682
headers: {
@@ -70,14 +86,14 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
7086
});
7187
}
7288

73-
private async _saveDownloadResponse(response: AxiosResponse, combo: ThunderstoreCombo, callback: (progress: number, status: number, err: R2Error | null) => void): Promise<void> {
89+
private async _saveDownloadResponse(response: AxiosResponse, combo: ThunderstoreCombo, callback: (progress: number, downloadedSize: number, status: number, err: R2Error | null) => void): Promise<void> {
7490
const buf: Buffer = Buffer.from(response.data)
75-
callback(100, StatusEnum.PENDING, null);
91+
callback(100, combo.getVersion().getFileSize(), StatusEnum.PENDING, null);
7692
await this.saveToFile(buf, combo, (success: boolean, error?: R2Error) => {
7793
if (success) {
78-
callback(100, StatusEnum.SUCCESS, error || null);
94+
callback(100, combo.getVersion().getFileSize(), StatusEnum.SUCCESS, null);
7995
} else {
80-
callback(100, StatusEnum.FAILURE, error || null);
96+
callback(100, combo.getVersion().getFileSize(), StatusEnum.FAILURE, error || null);
8197
}
8298
});
8399
}
@@ -120,4 +136,14 @@ export default class BetterThunderstoreDownloader extends ThunderstoreDownloader
120136
}
121137
}
122138

139+
public async getTotalDownloadSizeInBytes(combos: ThunderstoreCombo[], settings: ManagerSettings): Promise<number> {
140+
const filteredList = combos.filter(async value => !(await this.isVersionAlreadyDownloaded(value)) || settings.getContext().global.ignoreCache)
141+
.map(value => value.getVersion().getFileSize());
142+
if (filteredList.length > 0) {
143+
return filteredList.reduce((previousValue, currentValue) => previousValue + currentValue) || 0;
144+
} else {
145+
return 0;
146+
}
147+
}
148+
123149
}

src/store/modules/DownloadModule.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ interface DownloadProgress {
2424
profile: ImmutableProfile;
2525
modName: string;
2626
downloadProgress: number;
27+
downloadedSize: number;
28+
totalDownloadSize: number;
2729
installProgress: number;
2830
status: DownloadStatusEnum;
2931
}
3032

3133
interface UpdateObject {
3234
downloadId: UUID;
3335
downloadProgress?: number;
36+
downloadedSize?: number;
37+
totalDownloadSize?: number;
3438
installProgress?: number;
3539
modName?: string;
3640
status?: DownloadStatusEnum;
@@ -77,12 +81,12 @@ export const DownloadModule = {
7781
dispatch('retryDownload', { download });
7882
},
7983

80-
_addDownload({state}, params: {
84+
async _addDownload({state, rootGetters}, params: {
8185
combos: ThunderstoreCombo[],
8286
installMode: InstallMode,
8387
game: Game,
8488
profile: ImmutableProfile
85-
}): UUID {
89+
}): Promise<UUID> {
8690
const { combos, installMode, game, profile } = params;
8791
const downloadId = UUID.create();
8892
const downloadObject: DownloadProgress = {
@@ -93,6 +97,8 @@ export const DownloadModule = {
9397
profile,
9498
modName: '',
9599
downloadProgress: 0,
100+
downloadedSize: 0,
101+
totalDownloadSize: 0,
96102
installProgress: 0,
97103
status: DownloadStatusEnum.DOWNLOADING
98104
};
@@ -115,6 +121,7 @@ export const DownloadModule = {
115121
}) {
116122
const { combos, game, installMode, profile, hideModal } = params;
117123
let downloadId: UUID | undefined;
124+
const settings: ManagerSettings = rootGetters['settings'];
118125

119126
try {
120127
if (!hideModal) {
@@ -123,6 +130,7 @@ export const DownloadModule = {
123130
downloadId = await dispatch('_addDownload', { combos, installMode, game, profile });
124131
const installedMods = throwForR2Error(await ProfileModList.getModList(profile));
125132
const modsWithDependencies = await getFullDependencyList(combos, game, installedMods, installMode);
133+
commit('updateDownload', { downloadId, totalDownloadSize: await ThunderstoreDownloaderProvider.instance.getTotalDownloadSizeInBytes(modsWithDependencies, settings) });
126134
await dispatch('_download', { combos: modsWithDependencies, downloadId });
127135
commit('setInstalling', downloadId);
128136
await dispatch('_installModsAndResolveConflicts', { combos: modsWithDependencies, profile, downloadId });
@@ -146,24 +154,25 @@ export const DownloadModule = {
146154
}
147155
},
148156

149-
async downloadToCache({state}, params: {
157+
async downloadToCache({state, rootGetters}, params: {
150158
combos: ThunderstoreCombo[],
151-
progressCallback: (progress: number, modName: string, status: number, err: R2Error | null) => void
159+
progressCallback: (progress: number, downloadedSize: number, totalDownloadSize: number, modName: string, status: number, err: R2Error | null) => void
152160
}) {
153161
const { combos, progressCallback } = params;
154-
await ThunderstoreDownloaderProvider.instance.download(combos, state.ignoreCache, progressCallback);
162+
await ThunderstoreDownloaderProvider.instance.download(combos, state.ignoreCache, rootGetters['settings'], progressCallback);
155163
},
156164

157-
async _download({state, commit, dispatch}, params: {
165+
async _download({state, commit, dispatch, rootGetters}, params: {
158166
combos: ThunderstoreCombo[],
159167
downloadId: UUID
160168
}) {
161169
try {
162170
await ThunderstoreDownloaderProvider.instance.download(
163171
params.combos,
164172
state.ignoreCache,
165-
(downloadProgress, modName, status, err) => {
166-
dispatch('_downloadProgressCallback', { downloadId: params.downloadId, downloadProgress, modName, status, err });
173+
rootGetters['settings'],
174+
(progress, downloadedSize, totalDownloadSize, modName, status, err) => {
175+
dispatch('_downloadProgressCallback', { downloadId: params.downloadId, downloadProgress: progress, downloadedSize, totalDownloadSize, modName, status, err });
167176
}
168177
);
169178
} catch (e) {
@@ -199,21 +208,23 @@ export const DownloadModule = {
199208
async _downloadProgressCallback({commit}, params: {
200209
downloadId: UUID,
201210
downloadProgress: number,
211+
downloadedSize: number,
212+
totalDownloadSize: number,
202213
modName: string,
203-
status: number,
214+
status: DownloadStatusEnum,
204215
err: R2Error | null
205216
}) {
206-
const { downloadId, downloadProgress, modName, status, err} = params;
217+
const { downloadId, downloadProgress, downloadedSize, totalDownloadSize, modName, status, err} = params;
207218

208219
if (status === StatusEnum.FAILURE) {
209220
commit('closeDownloadProgressModal', null, { root: true });
210-
commit('setFailed', params.downloadId);
211-
if (params.err !== null) {
212-
DownloadUtils.addSolutionsToError(params.err);
213-
throw params.err;
221+
commit('setFailed', downloadId);
222+
if (err !== null) {
223+
DownloadUtils.addSolutionsToError(err);
224+
throw err;
214225
}
215226
} else if (status === StatusEnum.PENDING || status === StatusEnum.SUCCESS) {
216-
commit('updateDownload', { downloadId, modName, downloadProgress });
227+
commit('updateDownload', { downloadId, modName, downloadProgress, downloadedSize, totalDownloadSize });
217228
}
218229
},
219230
},

src/utils/DownloadUtils.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,3 @@ export function addSolutionsToError(err: R2Error): void {
1717
err.solution = 'Using "Change data folder" option in the settings to select a shorter path might solve the issue';
1818
}
1919
}
20-
21-
export function generateProgressPercentage(progress: number, currentIndex: number, total: number): number {
22-
if (progress === 100 && currentIndex === total) {
23-
return 100;
24-
}
25-
const completedProgress = (currentIndex / total) * 100;
26-
const totalProgressPercentage = completedProgress + (progress * 1/total)
27-
return Math.floor(totalProgressPercentage);
28-
}

0 commit comments

Comments
 (0)