Show installation progress in update status bar entry on Windows#292970
Show installation progress in update status bar entry on Windows#292970
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds installation progress reporting to the Windows update status bar entry. When VS Code is updating on Windows, users will now see a progress percentage in both the status bar text and tooltip, providing better visibility into the installation progress.
Changes:
- Modified the
Updatingstate type to include optionalcurrentProgressandmaxProgressfields for tracking installation progress - Updated the Windows update service to poll a progress file written by the Inno Setup installer and update the state with progress information
- Enhanced the status bar UI to display progress percentage when available, with a visual progress bar in the tooltip
- Modified the Inno Setup installer script to write progress information to a file during background updates
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/vs/platform/update/common/update.ts | Extended the Updating state type and factory function to include optional progress tracking fields |
| src/vs/platform/update/electron-main/updateService.win32.ts | Added polling logic to read progress from a file and update the state during installation |
| src/vs/workbench/contrib/update/browser/updateStatusBarEntry.ts | Enhanced UI to display progress percentage in status bar text and tooltip with a visual progress bar |
| build/win32/code.iss | Added Inno Setup callback to write installation progress to a file and cleanup logic for the progress file |
Comments suppressed due to low confidence (3)
src/vs/platform/update/electron-main/updateService.win32.ts:348
- The progress file created during installation is not cleaned up by the Node.js code when the update completes successfully via the ready mutex path or when the child process exits. While the Inno Setup installer does clean up the file in line 1642 of code.iss, if the installer crashes or is terminated abnormally, the progress file will remain in the cache directory.
Consider adding cleanup code to delete the progress file after the ready mutex is detected, or when the child process exits (in the 'exit' event handler at line 316).
// poll for mutex-ready
pollUntil(() => mutex.isActive(readyMutexName))
.then(() => this.setState(State.Ready(update, explicit, this._overwrite)));
}
src/vs/platform/update/electron-main/updateService.win32.ts:347
- There's a potential race condition where both
pollProgressand the existingpollUntilfor the ready mutex could complete at similar times. When the ready mutex becomes active, thepollUntilwill callsetState(State.Ready(...))at line 347, but thepollProgressloop at line 326 checks!mutex.isActive(readyMutexName)as its exit condition. If there's a timing issue, the polling loop could make one more setState call after the Ready state has been set, potentially updating the state unnecessarily or causing inconsistencies.
Consider adding a flag or ensuring the polling loop is properly cancelled when the ready state is reached.
// Poll for progress and ready mutex
const pollProgress = async () => {
while (this.state.type === StateType.Updating && !mutex.isActive(readyMutexName)) {
try {
const progressContent = await readFile(progressFilePath, 'utf8');
const [currentStr, maxStr] = progressContent.split(',');
const currentProgress = parseInt(currentStr, 10);
const maxProgress = parseInt(maxStr, 10);
if (!isNaN(currentProgress) && !isNaN(maxProgress) && this.state.type === StateType.Updating) {
this.setState(State.Updating(update, currentProgress, maxProgress));
}
} catch {
// Progress file may not exist yet or be locked, ignore
}
await timeout(500);
}
};
// Start polling for progress
pollProgress();
// poll for mutex-ready
pollUntil(() => mutex.isActive(readyMutexName))
.then(() => this.setState(State.Ready(update, explicit, this._overwrite)));
src/vs/workbench/contrib/update/browser/updateStatusBarEntry.ts:330
- The percentage calculation doesn't bound the result to a maximum of 100%. If
currentProgressexceedsmaxProgress(which could happen due to timing issues or installer bugs), the percentage could exceed 100%, potentially causing the progress bar to overflow its container or display misleading information like "105%".
Consider capping the percentage at 100% using Math.min(Math.round((currentProgress / maxProgress) * 100), 100) to handle edge cases gracefully.
const percentage = Math.round((currentProgress / maxProgress) * 100);
const progressContainer = dom.append(container, dom.$('.progress-container'));
const progressBar = dom.append(progressContainer, dom.$('.progress-bar'));
const progressFill = dom.append(progressBar, dom.$('.progress-fill'));
progressFill.style.width = `${percentage}%`;
src/vs/workbench/contrib/update/browser/updateStatusBarEntry.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (2)
src/vs/platform/update/electron-main/updateService.win32.ts:365
pollProgress()is started without awaiting/handling rejection. If anything throws outside the inner try/catch (e.g. mutex access), this can become an unhandled promise rejection. Consider invoking it withvoid …and attaching a.catch(...)(or wrapping the loop body in a top-level try/catch) to ensure failures are contained/logged.
// Poll for progress and ready mutex
const pollProgress = async () => {
while (this.state.type === StateType.Updating && !mutex.isActive(readyMutexName)) {
try {
const progressContent = await readFile(progressFilePath, 'utf8');
const [currentStr, maxStr] = progressContent.split(',');
const currentProgress = parseInt(currentStr, 10);
const maxProgress = parseInt(maxStr, 10);
if (!isNaN(currentProgress) && !isNaN(maxProgress) && this.state.type === StateType.Updating) {
this.setState(State.Updating(update, currentProgress, maxProgress));
}
} catch {
// Progress file may not exist yet or be locked, ignore
}
await timeout(500);
}
};
// Start polling for progress
pollProgress();
src/vs/platform/update/electron-main/updateService.win32.ts:359
- The progress polling loop calls
setState(State.Updating(...))every 500ms even if the parsed values haven’t changed, which will fireonStateChangeand logupdate#setStaterepeatedly. Consider comparing against the current state’scurrentProgress/maxProgressand only callingsetStatewhen the values actually change.
const progressContent = await readFile(progressFilePath, 'utf8');
const [currentStr, maxStr] = progressContent.split(',');
const currentProgress = parseInt(currentStr, 10);
const maxProgress = parseInt(maxStr, 10);
if (!isNaN(currentProgress) && !isNaN(maxProgress) && this.state.type === StateType.Updating) {
this.setState(State.Updating(update, currentProgress, maxProgress));
}
} catch {
// Progress file may not exist yet or be locked, ignore
}
await timeout(500);
Fixes #293872