mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-26 18:27:38 +01:00
committed by
GitHub
parent
beea143b43
commit
e549d660ed
@@ -266,7 +266,6 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem
|
||||
// unset if false
|
||||
metaData.isMachineScoped = metaData.isMachineScoped || undefined;
|
||||
metaData.isBuiltin = metaData.isBuiltin || undefined;
|
||||
metaData.installedTimestamp = metaData.installedTimestamp || undefined;
|
||||
manifest.__metadata = { ...manifest.__metadata, ...metaData };
|
||||
|
||||
await this.fileService.writeFile(joinPath(extensionLocation, 'package.json'), VSBuffer.fromString(JSON.stringify(manifest, null, '\t')));
|
||||
|
||||
@@ -30,11 +30,22 @@ export class ExtensionsLifecycle extends Disposable {
|
||||
const script = this.parseScript(extension, 'uninstall');
|
||||
if (script) {
|
||||
this.logService.info(extension.identifier.id, extension.manifest.version, `Running post uninstall script`);
|
||||
await this.processesLimiter.queue(() =>
|
||||
this.runLifecycleHook(script.script, 'uninstall', script.args, true, extension)
|
||||
.then(() => this.logService.info(extension.identifier.id, extension.manifest.version, `Finished running post uninstall script`), err => this.logService.error(extension.identifier.id, extension.manifest.version, `Failed to run post uninstall script: ${err}`)));
|
||||
await this.processesLimiter.queue(async () => {
|
||||
try {
|
||||
await this.runLifecycleHook(script.script, 'uninstall', script.args, true, extension);
|
||||
this.logService.info(`Finished running post uninstall script`, extension.identifier.id, extension.manifest.version);
|
||||
} catch (error) {
|
||||
this.logService.error('Failed to run post uninstall script', extension.identifier.id, extension.manifest.version);
|
||||
this.logService.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
await Promises.rm(this.getExtensionStoragePath(extension));
|
||||
} catch (error) {
|
||||
this.logService.error('Error while removing extension storage path', extension.identifier.id);
|
||||
this.logService.error(error);
|
||||
}
|
||||
return Promises.rm(this.getExtensionStoragePath(extension)).then(undefined, e => this.logService.error('Error while removing extension storage path', e));
|
||||
}
|
||||
|
||||
private parseScript(extension: ILocalExtension, type: string): { script: string; args: string[] } | null {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Promises, Queue } from 'vs/base/common/async';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
@@ -39,7 +40,7 @@ import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/ex
|
||||
import { DidChangeProfileExtensionsEvent, ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher';
|
||||
import { ExtensionType, IExtension, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions';
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { FileChangesEvent, FileChangeType, FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
@@ -56,9 +57,10 @@ interface InstallableExtension {
|
||||
export const INativeServerExtensionManagementService = refineServiceDecorator<IExtensionManagementService, INativeServerExtensionManagementService>(IExtensionManagementService);
|
||||
export interface INativeServerExtensionManagementService extends IExtensionManagementService {
|
||||
readonly _serviceBrand: undefined;
|
||||
scanAllUserInstalledExtensions(): Promise<ILocalExtension[]>;
|
||||
scanInstalledExtensionAtLocation(location: URI): Promise<ILocalExtension | null>;
|
||||
markAsUninstalled(...extensions: IExtension[]): Promise<void>;
|
||||
removeUninstalledExtensions(): Promise<void>;
|
||||
getAllUserInstalled(): Promise<ILocalExtension[]>;
|
||||
}
|
||||
|
||||
export class ExtensionManagementService extends AbstractExtensionManagementService implements INativeServerExtensionManagementService {
|
||||
@@ -132,10 +134,14 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
return this.extensionsScanner.scanExtensions(type ?? null, profileLocation);
|
||||
}
|
||||
|
||||
getAllUserInstalled(): Promise<ILocalExtension[]> {
|
||||
scanAllUserInstalledExtensions(): Promise<ILocalExtension[]> {
|
||||
return this.extensionsScanner.scanAllUserExtensions(false);
|
||||
}
|
||||
|
||||
scanInstalledExtensionAtLocation(location: URI): Promise<ILocalExtension | null> {
|
||||
return this.extensionsScanner.scanUserExtensionAtLocation(location);
|
||||
}
|
||||
|
||||
async install(vsix: URI, options: InstallVSIXOptions = {}): Promise<ILocalExtension> {
|
||||
this.logService.trace('ExtensionManagementService#install', vsix.toString());
|
||||
|
||||
@@ -208,8 +214,13 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
return this.extensionsScanner.setUninstalled(...extensions);
|
||||
}
|
||||
|
||||
removeUninstalledExtensions(): Promise<void> {
|
||||
return this.extensionsScanner.cleanUp();
|
||||
async removeUninstalledExtensions(): Promise<void> {
|
||||
this.logService.trace('ExtensionManagementService#removeUninstalledExtensions');
|
||||
try {
|
||||
await this.extensionsScanner.cleanUp();
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async download(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
@@ -361,15 +372,28 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi
|
||||
}
|
||||
|
||||
private async addExtensionsToProfile(extensions: ILocalExtension[], profileLocation: URI): Promise<void> {
|
||||
await this.setInstalled(extensions);
|
||||
await this.extensionsProfileScannerService.addExtensionsToProfile(extensions.map(local => ([local, undefined])), profileLocation);
|
||||
this._onDidInstallExtensions.fire(extensions.map(local => ({ local, identifier: local.identifier, operation: InstallOperation.None, profileLocation })));
|
||||
}
|
||||
|
||||
private async setInstalled(extensions: ILocalExtension[]): Promise<void> {
|
||||
const uninstalled = await this.extensionsScanner.getUninstalledExtensions();
|
||||
for (const extension of extensions) {
|
||||
const extensionKey = ExtensionKey.create(extension);
|
||||
if (!uninstalled[extensionKey.toString()]) {
|
||||
continue;
|
||||
}
|
||||
this.logService.trace('Removing the extension from uninstalled list:', extensionKey.id);
|
||||
await this.extensionsScanner.setInstalled(extensionKey);
|
||||
this.logService.info('Removed the extension from uninstalled list:', extensionKey.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionsScanner extends Disposable {
|
||||
|
||||
private readonly uninstalledPath: string;
|
||||
private readonly uninstalledResource: URI;
|
||||
private readonly uninstalledFileLimiter: Queue<any>;
|
||||
|
||||
private readonly _onExtract = this._register(new Emitter<URI>());
|
||||
@@ -383,7 +407,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
this.uninstalledPath = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete').fsPath;
|
||||
this.uninstalledResource = joinPath(this.extensionsScannerService.userExtensionsLocation, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
}
|
||||
|
||||
@@ -484,19 +508,13 @@ export class ExtensionsScanner extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
async setInstalled(extensionKey: ExtensionKey): Promise<ILocalExtension | null> {
|
||||
async setInstalled(extensionKey: ExtensionKey): Promise<void> {
|
||||
await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]);
|
||||
const userExtensions = await this.scanAllUserExtensions(true);
|
||||
const localExtension = userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)) || null;
|
||||
if (!localExtension) {
|
||||
return null;
|
||||
}
|
||||
return this.updateMetadata(localExtension, { installedTimestamp: Date.now() });
|
||||
}
|
||||
|
||||
async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise<void> {
|
||||
this.logService.trace(`Deleting ${type} extension from disk`, extension.identifier.id, extension.location.fsPath);
|
||||
await pfs.Promises.rm(extension.location.fsPath);
|
||||
await this.fileService.del(extension.location, { recursive: true });
|
||||
this.logService.info('Deleted from disk', extension.identifier.id, extension.location.fsPath);
|
||||
}
|
||||
|
||||
@@ -509,10 +527,11 @@ export class ExtensionsScanner extends Disposable {
|
||||
return this.uninstalledFileLimiter.queue(async () => {
|
||||
let raw: string | undefined;
|
||||
try {
|
||||
raw = await pfs.Promises.readFile(this.uninstalledPath, 'utf8');
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
const content = await this.fileService.readFile(this.uninstalledResource, 'utf8');
|
||||
raw = content.value.toString();
|
||||
} catch (error) {
|
||||
if (toFileOperationResult(error) !== FileOperationResult.FILE_NOT_FOUND) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,9 +545,9 @@ export class ExtensionsScanner extends Disposable {
|
||||
if (updateFn) {
|
||||
updateFn(uninstalled);
|
||||
if (Object.keys(uninstalled).length) {
|
||||
await pfs.Promises.writeFile(this.uninstalledPath, JSON.stringify(uninstalled));
|
||||
await this.fileService.writeFile(this.uninstalledResource, VSBuffer.fromString(JSON.stringify(uninstalled)));
|
||||
} else {
|
||||
await pfs.Promises.rm(this.uninstalledPath);
|
||||
await this.fileService.del(this.uninstalledResource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,6 +635,8 @@ export class ExtensionsScanner extends Disposable {
|
||||
|
||||
private async removeUninstalledExtensions(): Promise<void> {
|
||||
const uninstalled = await this.getUninstalledExtensions();
|
||||
this.logService.debug(`Removing uninstalled extensions:`, Object.keys(uninstalled));
|
||||
|
||||
const extensions = await this.extensionsScannerService.scanUserExtensions({ includeAllVersions: true, includeUninstalled: true, includeInvalid: true }); // All user extensions
|
||||
const installed: Set<string> = new Set<string>();
|
||||
for (const e of extensions) {
|
||||
@@ -623,15 +644,22 @@ export class ExtensionsScanner extends Disposable {
|
||||
installed.add(e.identifier.id.toLowerCase());
|
||||
}
|
||||
}
|
||||
const byExtension = groupByExtension(extensions, e => e.identifier);
|
||||
await Promises.settled(byExtension.map(async e => {
|
||||
const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];
|
||||
if (!installed.has(latest.identifier.id.toLowerCase())) {
|
||||
await this.beforeRemovingExtension(await this.toLocalExtension(latest));
|
||||
}
|
||||
}));
|
||||
const toRemove = extensions.filter(e => e.metadata /* Installed by VS Code */ && uninstalled[ExtensionKey.create(e).toString()]);
|
||||
await Promises.settled(toRemove.map(e => this.removeUninstalledExtension(e)));
|
||||
|
||||
try {
|
||||
// running post uninstall tasks for extensions that are not installed anymore
|
||||
const byExtension = groupByExtension(extensions, e => e.identifier);
|
||||
await Promises.settled(byExtension.map(async e => {
|
||||
const latest = e.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0];
|
||||
if (!installed.has(latest.identifier.id.toLowerCase())) {
|
||||
await this.beforeRemovingExtension(await this.toLocalExtension(latest));
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
const toRemove = extensions.filter(e => e.metadata /* Installed by System */ && uninstalled[ExtensionKey.create(e).toString()]);
|
||||
await Promise.allSettled(toRemove.map(e => this.removeUninstalledExtension(e)));
|
||||
}
|
||||
|
||||
private joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error {
|
||||
@@ -688,10 +716,15 @@ abstract class InstallExtensionTask extends AbstractExtensionTask<{ local: ILoca
|
||||
|
||||
this.logService.trace('Removing the extension from uninstalled list:', extensionKey.id);
|
||||
// If the same version of extension is marked as uninstalled, remove it from there and return the local.
|
||||
const local = await this.extensionsScanner.setInstalled(extensionKey);
|
||||
await this.extensionsScanner.setInstalled(extensionKey);
|
||||
this.logService.info('Removed the extension from uninstalled list:', extensionKey.id);
|
||||
|
||||
return local;
|
||||
const userExtensions = await this.extensionsScanner.scanAllUserExtensions(true);
|
||||
const localExtension = userExtensions.find(i => ExtensionKey.create(i).equals(extensionKey)) || null;
|
||||
if (!localExtension) {
|
||||
return null;
|
||||
}
|
||||
return this.extensionsScanner.updateMetadata(localExtension, { installedTimestamp: Date.now() });
|
||||
}
|
||||
|
||||
private async isUninstalled(extensionId: ExtensionKey): Promise<boolean> {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensio
|
||||
import { DidAddProfileExtensionsEvent, DidRemoveProfileExtensionsEvent, IExtensionsProfileScannerService, ProfileExtensionsEvent } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService';
|
||||
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
|
||||
import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
|
||||
import { ExtensionIdentifier, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { ExtensionIdentifier, IExtension, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
|
||||
@@ -106,18 +106,34 @@ export class ExtensionsWatcher extends Disposable {
|
||||
}
|
||||
|
||||
private async onDidRemoveExtensions(e: DidRemoveProfileExtensionsEvent): Promise<void> {
|
||||
let hasToUninstallExtensions = false;
|
||||
const extensionsToUninstall: IExtension[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const extension of e.extensions) {
|
||||
const key = this.getKey(extension.identifier, extension.version);
|
||||
if (e.error) {
|
||||
this.addExtensionWithKey(key, e.profileLocation);
|
||||
} else {
|
||||
this.removeExtensionWithKey(key, e.profileLocation);
|
||||
hasToUninstallExtensions = hasToUninstallExtensions || !this.allExtensions.has(key);
|
||||
if (!this.allExtensions.has(key)) {
|
||||
this.logService.debug('Extension is removed from all profiles', extension.identifier.id, extension.version);
|
||||
promises.push(this.extensionManagementService.scanInstalledExtensionAtLocation(extension.location)
|
||||
.then(result => {
|
||||
if (result) {
|
||||
extensionsToUninstall.push(result);
|
||||
} else {
|
||||
this.logService.info('Extension not found at the location', extension.location.toString());
|
||||
}
|
||||
}, error => this.logService.error(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasToUninstallExtensions) {
|
||||
await this.uninstallExtensionsNotInProfiles();
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
if (extensionsToUninstall.length) {
|
||||
await this.uninstallExtensionsNotInProfiles(extensionsToUninstall);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,9 +191,11 @@ export class ExtensionsWatcher extends Disposable {
|
||||
await this.uninstallExtensionsNotInProfiles();
|
||||
}
|
||||
|
||||
private async uninstallExtensionsNotInProfiles(): Promise<void> {
|
||||
const installed = await this.extensionManagementService.getAllUserInstalled();
|
||||
const toUninstall = installed.filter(installedExtension => installedExtension.installedTimestamp /* Installed by VS Code */ && !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
|
||||
private async uninstallExtensionsNotInProfiles(toUninstall?: IExtension[]): Promise<void> {
|
||||
if (!toUninstall) {
|
||||
const installed = await this.extensionManagementService.scanAllUserInstalledExtensions();
|
||||
toUninstall = installed.filter(installedExtension => !this.allExtensions.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version)));
|
||||
}
|
||||
if (toUninstall.length) {
|
||||
await this.extensionManagementService.markAsUninstalled(...toUninstall);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user