Path: blob/main/src/vs/workbench/services/integrity/electron-browser/integrityService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize } from '../../../../nls.js';6import Severity from '../../../../base/common/severity.js';7import { URI } from '../../../../base/common/uri.js';8import { ChecksumPair, IIntegrityService, IntegrityTestResult } from '../common/integrity.js';9import { ILifecycleService, LifecyclePhase } from '../../lifecycle/common/lifecycle.js';10import { IProductService } from '../../../../platform/product/common/productService.js';11import { INotificationService, NotificationPriority } from '../../../../platform/notification/common/notification.js';12import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';13import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';14import { IOpenerService } from '../../../../platform/opener/common/opener.js';15import { FileAccess, AppResourcePath } from '../../../../base/common/network.js';16import { IChecksumService } from '../../../../platform/checksum/common/checksumService.js';17import { ILogService } from '../../../../platform/log/common/log.js';1819interface IStorageData {20readonly dontShowPrompt: boolean;21readonly commit: string | undefined;22}2324class IntegrityStorage {2526private static readonly KEY = 'integrityService';2728private value: IStorageData | null;2930constructor(private readonly storageService: IStorageService) {31this.value = this._read();32}3334private _read(): IStorageData | null {35const jsonValue = this.storageService.get(IntegrityStorage.KEY, StorageScope.APPLICATION);36if (!jsonValue) {37return null;38}3940try {41return JSON.parse(jsonValue);42} catch (err) {43return null;44}45}4647get(): IStorageData | null {48return this.value;49}5051set(data: IStorageData | null): void {52this.value = data;53this.storageService.store(IntegrityStorage.KEY, JSON.stringify(this.value), StorageScope.APPLICATION, StorageTarget.MACHINE);54}55}5657export class IntegrityService implements IIntegrityService {5859declare readonly _serviceBrand: undefined;6061private readonly storage: IntegrityStorage;6263private readonly isPurePromise: Promise<IntegrityTestResult>;64isPure(): Promise<IntegrityTestResult> { return this.isPurePromise; }6566constructor(67@INotificationService private readonly notificationService: INotificationService,68@IStorageService storageService: IStorageService,69@ILifecycleService private readonly lifecycleService: ILifecycleService,70@IOpenerService private readonly openerService: IOpenerService,71@IProductService private readonly productService: IProductService,72@IChecksumService private readonly checksumService: IChecksumService,73@ILogService private readonly logService: ILogService74) {75this.storage = new IntegrityStorage(storageService);76this.isPurePromise = this._isPure();7778this._compute();79}8081private async _compute(): Promise<void> {82const { isPure } = await this.isPure();83if (isPure) {84return; // all is good85}8687this.logService.warn(`8889----------------------------------------------90*** Installation has been modified on disk ***91----------------------------------------------9293`);9495const storedData = this.storage.get();96if (storedData?.dontShowPrompt && storedData.commit === this.productService.commit) {97return; // Do not prompt98}99100this._showNotification();101}102103private async _isPure(): Promise<IntegrityTestResult> {104const expectedChecksums = this.productService.checksums || {};105106await this.lifecycleService.when(LifecyclePhase.Eventually);107108const allResults = await Promise.all(Object.keys(expectedChecksums).map(filename => this._resolve(<AppResourcePath>filename, expectedChecksums[filename])));109110let isPure = true;111for (let i = 0, len = allResults.length; i < len; i++) {112if (!allResults[i].isPure) {113isPure = false;114break;115}116}117118return {119isPure,120proof: allResults121};122}123124private async _resolve(filename: AppResourcePath, expected: string): Promise<ChecksumPair> {125const fileUri = FileAccess.asFileUri(filename);126127try {128const checksum = await this.checksumService.checksum(fileUri);129130return IntegrityService._createChecksumPair(fileUri, checksum, expected);131} catch (error) {132return IntegrityService._createChecksumPair(fileUri, '', expected);133}134}135136private static _createChecksumPair(uri: URI, actual: string, expected: string): ChecksumPair {137return {138uri: uri,139actual: actual,140expected: expected,141isPure: (actual === expected)142};143}144145private _showNotification(): void {146const checksumFailMoreInfoUrl = this.productService.checksumFailMoreInfoUrl;147const message = localize('integrity.prompt', "Your {0} installation appears to be corrupt. Please reinstall.", this.productService.nameShort);148if (checksumFailMoreInfoUrl) {149this.notificationService.prompt(150Severity.Warning,151message,152[153{154label: localize('integrity.moreInformation', "More Information"),155run: () => this.openerService.open(URI.parse(checksumFailMoreInfoUrl))156},157{158label: localize('integrity.dontShowAgain', "Don't Show Again"),159isSecondary: true,160run: () => this.storage.set({ dontShowPrompt: true, commit: this.productService.commit })161}162],163{164sticky: true,165priority: NotificationPriority.URGENT166}167);168} else {169this.notificationService.notify({170severity: Severity.Warning,171message,172sticky: true,173priority: NotificationPriority.URGENT174});175}176}177}178179registerSingleton(IIntegrityService, IntegrityService, InstantiationType.Delayed);180181182