Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898 (#15681)

* Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898

* Fixes and cleanup

* Distro

* Fix hygiene yarn

* delete no yarn lock changes file

* Fix hygiene

* Fix layer check

* Fix CI

* Skip lib checks

* Remove tests deleted in vs code

* Fix tests

* Distro

* Fix tests and add removed extension point

* Skip failing notebook tests for now

* Disable broken tests and cleanup build folder

* Update yarn.lock and fix smoke tests

* Bump sqlite

* fix contributed actions and file spacing

* Fix user data path

* Update yarn.locks

Co-authored-by: ADS Merger <karlb@microsoft.com>
This commit is contained in:
Charles Gagnon
2021-06-17 08:17:11 -07:00
committed by GitHub
parent fdcb97c7f7
commit 3cb2f552a6
2582 changed files with 124827 additions and 87099 deletions

View File

@@ -16,6 +16,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import * as semver from 'vs/base/common/semver/semver';
import { isWindows } from 'vs/base/common/platform';
import { Promises } from 'vs/base/common/async';
import { getErrorMessage } from 'vs/base/common/errors';
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
@@ -45,13 +46,26 @@ export class ExtensionsDownloader extends Disposable {
// Download only if vsix does not exist
if (!await this.fileService.exists(location)) {
// Download to temporary location first only if vsix does not exist
const tempLocation = joinPath(this.extensionsDownloadDir, `.${vsixName}`);
const tempLocation = joinPath(this.extensionsDownloadDir, `.${generateUuid()}`);
if (!await this.fileService.exists(tempLocation)) {
await this.extensionGalleryService.download(extension, tempLocation, operation);
}
// Rename temp location to original
await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
try {
// Rename temp location to original
await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
} catch (error) {
try {
await this.fileService.del(tempLocation);
} catch (e) { /* ignore */ }
if (error.code === 'ENOTEMPTY') {
this.logService.info(`Rename failed because vsix was downloaded by another source. So ignoring renaming.`, extension.identifier.id);
} else {
this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted the vsix from downloaded location`, tempLocation.path);
throw error;
}
}
}
return location;

View File

@@ -47,6 +47,8 @@ import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common
import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/extensionDownloader';
import { ExtensionsScanner, ILocalExtensionManifest, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner';
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher';
import { IFileService } from 'vs/platform/files/common/files';
const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled';
const INSTALL_ERROR_DOWNLOADING = 'downloading';
@@ -91,12 +93,19 @@ export class ExtensionManagementService extends Disposable implements IExtension
@optional(IDownloadService) private downloadService: IDownloadService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService fileService: IFileService,
) {
super();
const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle));
this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension)));
this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this));
this.extensionsDownloader = this._register(instantiationService.createInstance(ExtensionsDownloader));
const extensionsWatcher = this._register(new ExtensionsWatcher(this, fileService, environmentService, logService));
this._register(extensionsWatcher.onDidChangeExtensionsByAnotherSource(({ added, removed }) => {
added.forEach(extension => this._onDidInstallExtension.fire({ identifier: extension.identifier, operation: InstallOperation.None, local: extension }));
removed.forEach(extension => this._onDidUninstallExtension.fire({ identifier: extension }));
}));
this._register(toDisposable(() => {
this.installingExtensions.forEach(promise => promise.cancel());
@@ -327,7 +336,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
await this.setUninstalled(existingExtension);
await this.extensionsScanner.setUninstalled(existingExtension);
}
this.logService.info(`Extensions installed successfully:`, extension.identifier.id);
@@ -371,7 +380,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled"));
}
await this.setUninstalled(extension);
await this.extensionsScanner.setUninstalled(extension);
try {
await this.extensionsScanner.removeUninstalledExtension(extension);
} catch (e) {
@@ -397,12 +406,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
publisherDisplayName: extension.publisherDisplayName,
};
let zipPath;
let zipPath: string | undefined;
try {
this.logService.trace('Started downloading extension:', extension.identifier.id);
const zip = await this.extensionsDownloader.downloadExtension(extension, operation);
zipPath = (await this.extensionsDownloader.downloadExtension(extension, operation)).fsPath;
this.logService.info('Downloaded extension:', extension.identifier.id, zipPath);
zipPath = zip.fsPath;
} catch (error) {
throw new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING);
}
@@ -439,11 +447,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.logService.trace('Removing the extension from uninstalled list:', identifierWithVersion.id);
// If the same version of extension is marked as uninstalled, remove it from there and return the local.
await this.unsetUninstalled(identifierWithVersion);
const local = await this.extensionsScanner.setInstalled(identifierWithVersion);
this.logService.info('Removed the extension from uninstalled list:', identifierWithVersion.id);
const installed = await this.getInstalled(ExtensionType.User);
return installed.find(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion)) || null;
return local;
}
private async extractAndInstall({ zipPath, identifierWithVersion, metadata }: InstallableExtension, token: CancellationToken): Promise<ILocalExtension> {
@@ -664,7 +671,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
// Set all versions of the extension as uninstalled
promise = createCancelablePromise(async () => {
const userExtensions = await this.extensionsScanner.scanUserExtensions(false);
await this.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier)));
await this.extensionsScanner.setUninstalled(...userExtensions.filter(u => areSameExtensions(u.identifier, local.identifier)));
});
this.uninstallingExtensions.set(local.identifier.id, promise);
promise.finally(() => this.uninstallingExtensions.delete(local.identifier.id));
@@ -702,28 +709,15 @@ export class ExtensionManagementService extends Disposable implements IExtension
return uninstalled.length === 1;
}
private filterUninstalled(...identifiers: ExtensionIdentifierWithVersion[]): Promise<string[]> {
return this.extensionsScanner.withUninstalledExtensions(allUninstalled => {
const uninstalled: string[] = [];
for (const identifier of identifiers) {
if (!!allUninstalled[identifier.key()]) {
uninstalled.push(identifier.key());
}
private async filterUninstalled(...identifiers: ExtensionIdentifierWithVersion[]): Promise<string[]> {
const uninstalled: string[] = [];
const allUninstalled = await this.extensionsScanner.getUninstalledExtensions();
for (const identifier of identifiers) {
if (!!allUninstalled[identifier.key()]) {
uninstalled.push(identifier.key());
}
return uninstalled;
});
}
private setUninstalled(...extensions: ILocalExtension[]): Promise<{ [id: string]: boolean }> {
const ids: ExtensionIdentifierWithVersion[] = extensions.map(e => new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version));
return this.extensionsScanner.withUninstalledExtensions(uninstalled => {
ids.forEach(id => uninstalled[id.key()] = true);
return uninstalled;
});
}
private unsetUninstalled(extensionIdentifier: ExtensionIdentifierWithVersion): Promise<void> {
return this.extensionsScanner.withUninstalledExtensions<void>(uninstalled => delete uninstalled[extensionIdentifier.key()]);
}
return uninstalled;
}
getExtensionsReport(): Promise<IReportedExtension[]> {

View File

@@ -25,8 +25,6 @@ type IExeBasedExtensionTips = {
export class ExtensionTipsService extends BaseExtensionTipsService {
_serviceBrand: any;
private readonly allImportantExecutableTips: Map<string, IExeBasedExtensionTips> = new Map<string, IExeBasedExtensionTips>();
private readonly allOtherExecutableTips: Map<string, IExeBasedExtensionTips> = new Map<string, IExeBasedExtensionTips>();
@@ -59,11 +57,11 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
}
}
getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
override getImportantExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.getValidExecutableBasedExtensionTips(this.allImportantExecutableTips);
}
getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
override getOtherExecutableBasedTips(): Promise<IExecutableBasedExtensionTip[]> {
return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips);
}

View File

@@ -24,6 +24,10 @@ import { isWindows } from 'vs/base/common/platform';
import { flatten } from 'vs/base/common/arrays';
import { IStringDictionary } from 'vs/base/common/collections';
import { FileAccess } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { basename } from 'vs/base/common/resources';
import { generateUuid } from 'vs/base/common/uuid';
import { getErrorMessage } from 'vs/base/common/errors';
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser';
@@ -31,7 +35,8 @@ const INSTALL_ERROR_EXTRACTING = 'extracting';
const INSTALL_ERROR_DELETING = 'deleting';
const INSTALL_ERROR_RENAMING = 'renaming';
export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; isBuiltin: boolean }>;
export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; isBuiltin: boolean; }>;
type IStoredMetadata = IMetadata & { installedTimestamp: number | undefined };
export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata };
type IRelaxedLocalExtension = Omit<ILocalExtension, 'isBuiltin'> & { isBuiltin: boolean };
@@ -44,6 +49,7 @@ export class ExtensionsScanner extends Disposable {
constructor(
private readonly beforeRemovingExtension: (e: ILocalExtension) => Promise<void>,
@IFileService private readonly fileService: IFileService,
@ILogService private readonly logService: ILogService,
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IProductService private readonly productService: IProductService,
@@ -97,7 +103,7 @@ export class ExtensionsScanner extends Disposable {
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, token: CancellationToken): Promise<ILocalExtension> {
const folderName = identifierWithVersion.key();
const tempPath = path.join(this.extensionsPath, `.${folderName}`);
const tempPath = path.join(this.extensionsPath, `.${generateUuid()}`);
const extensionPath = path.join(this.extensionsPath, folderName);
try {
@@ -110,20 +116,29 @@ export class ExtensionsScanner extends Disposable {
}
await this.extractAtLocation(identifierWithVersion, zipPath, tempPath, token);
let local = await this.scanExtension(URI.file(tempPath), ExtensionType.User);
if (!local) {
throw new Error(localize('cannot read', "Cannot read the extension from {0}", tempPath));
}
await this.storeMetadata(local, { installedTimestamp: Date.now() });
try {
await this.rename(identifierWithVersion, tempPath, extensionPath, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
this.logService.info('Renamed to', extensionPath);
} catch (error) {
this.logService.info('Rename failed. Deleting from extracted location', tempPath);
try {
pfs.rimraf(tempPath);
await pfs.rimraf(tempPath);
} catch (e) { /* ignore */ }
throw error;
if (error.code === 'ENOTEMPTY') {
this.logService.info(`Rename failed because extension was installed by another source. So ignoring renaming.`, identifierWithVersion.id);
} else {
this.logService.info(`Rename failed because of ${getErrorMessage(error)}. Deleted from extracted location`, tempPath);
throw error;
}
}
let local: ILocalExtension | null = null;
try {
local = await this.scanExtension(folderName, this.extensionsPath, ExtensionType.User);
local = await this.scanExtension(URI.file(extensionPath), ExtensionType.User);
} catch (e) { /*ignore */ }
if (local) {
@@ -134,23 +149,46 @@ export class ExtensionsScanner extends Disposable {
async saveMetadataForLocalExtension(local: ILocalExtension, metadata: IMetadata): Promise<ILocalExtension> {
this.setMetadata(local, metadata);
await this.storeMetadata(local, { ...metadata, installedTimestamp: local.installedTimestamp });
return local;
}
private async storeMetadata(local: ILocalExtension, storedMetadata: IStoredMetadata): Promise<ILocalExtension> {
// unset if false
metadata.isMachineScoped = metadata.isMachineScoped || undefined;
metadata.isBuiltin = metadata.isBuiltin || undefined;
storedMetadata.isMachineScoped = storedMetadata.isMachineScoped || undefined;
storedMetadata.isBuiltin = storedMetadata.isBuiltin || undefined;
storedMetadata.installedTimestamp = storedMetadata.installedTimestamp || undefined;
const manifestPath = path.join(local.location.fsPath, 'package.json');
const raw = await fs.promises.readFile(manifestPath, 'utf8');
const { manifest } = await this.parseManifest(raw);
(manifest as ILocalExtensionManifest).__metadata = metadata;
(manifest as ILocalExtensionManifest).__metadata = storedMetadata;
await pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t'));
return local;
}
getUninstalledExtensions(): Promise<{ [id: string]: boolean; }> {
return this.withUninstalledExtensions(uninstalled => uninstalled);
getUninstalledExtensions(): Promise<IStringDictionary<boolean>> {
return this.withUninstalledExtensions();
}
async withUninstalledExtensions<T>(fn: (uninstalled: IStringDictionary<boolean>) => T): Promise<T> {
async setUninstalled(...extensions: ILocalExtension[]): Promise<void> {
const ids: ExtensionIdentifierWithVersion[] = extensions.map(e => new ExtensionIdentifierWithVersion(e.identifier, e.manifest.version));
await this.withUninstalledExtensions(uninstalled => {
ids.forEach(id => uninstalled[id.key()] = true);
});
}
async setInstalled(identifierWithVersion: ExtensionIdentifierWithVersion): Promise<ILocalExtension | null> {
await this.withUninstalledExtensions(uninstalled => delete uninstalled[identifierWithVersion.key()]);
const installed = await this.scanExtensions(ExtensionType.User);
const localExtension = installed.find(i => new ExtensionIdentifierWithVersion(i.identifier, i.manifest.version).equals(identifierWithVersion)) || null;
if (!localExtension) {
return null;
}
await this.storeMetadata(localExtension, { installedTimestamp: Date.now() });
return this.scanExtension(localExtension.location, ExtensionType.User);
}
private async withUninstalledExtensions(updateFn?: (uninstalled: IStringDictionary<boolean>) => void): Promise<IStringDictionary<boolean>> {
return this.uninstalledFileLimiter.queue(async () => {
let raw: string | undefined;
try {
@@ -168,15 +206,16 @@ export class ExtensionsScanner extends Disposable {
} catch (e) { /* ignore */ }
}
const result = fn(uninstalled);
if (Object.keys(uninstalled).length) {
await pfs.writeFile(this.uninstalledPath, JSON.stringify(uninstalled));
} else {
await pfs.rimraf(this.uninstalledPath);
if (updateFn) {
updateFn(uninstalled);
if (Object.keys(uninstalled).length) {
await pfs.writeFile(this.uninstalledPath, JSON.stringify(uninstalled));
} else {
await pfs.rimraf(this.uninstalledPath);
}
}
return result;
return uninstalled;
});
}
@@ -237,33 +276,41 @@ export class ExtensionsScanner extends Disposable {
private async scanExtensionsInDir(dir: string, type: ExtensionType): Promise<ILocalExtension[]> {
const limiter = new Limiter<any>(10);
const extensionsFolders = await pfs.readdir(dir);
const extensions = await Promise.all<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, dir, type))));
return extensions.filter(e => e && e.identifier);
const stat = await this.fileService.resolve(URI.file(dir));
if (stat.children) {
const extensions = await Promise.all<ILocalExtension>(stat.children.filter(c => c.isDirectory)
.map(c => limiter.queue(async () => {
if (type === ExtensionType.User && basename(c.resource).indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
return null;
}
return this.scanExtension(c.resource, type);
})));
return extensions.filter(e => e && e.identifier);
}
return [];
}
private async scanExtension(folderName: string, root: string, type: ExtensionType): Promise<ILocalExtension | null> {
if (type === ExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user extension folder starting with `.`
return null;
}
const extensionPath = path.join(root, folderName);
private async scanExtension(extensionLocation: URI, type: ExtensionType): Promise<ILocalExtension | null> {
try {
const children = await pfs.readdir(extensionPath);
const { manifest, metadata } = await this.readManifest(extensionPath);
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)) : undefined;
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)) : undefined;
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
const local = <ILocalExtension>{ type, identifier, manifest, location: URI.file(extensionPath), readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false, isBuiltin: type === ExtensionType.System };
if (metadata) {
this.setMetadata(local, metadata);
const stat = await this.fileService.resolve(extensionLocation);
if (stat.children) {
const { manifest, metadata } = await this.readManifest(extensionLocation.fsPath);
const readmeUrl = stat.children.find(({ name }) => /^readme(\.txt|\.md|)$/i.test(name))?.resource;
const changelogUrl = stat.children.find(({ name }) => /^changelog(\.txt|\.md|)$/i.test(name))?.resource;
const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) };
const local = <ILocalExtension>{ type, identifier, manifest, location: extensionLocation, readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false, isBuiltin: type === ExtensionType.System };
if (metadata) {
this.setMetadata(local, metadata);
local.installedTimestamp = metadata.installedTimestamp;
}
return local;
}
return local;
} catch (e) {
this.logService.trace(e);
return null;
if (type !== ExtensionType.System) {
this.logService.trace(e);
}
}
return null;
}
private async scanDefaultSystemExtensions(): Promise<ILocalExtension[]> {
@@ -344,7 +391,7 @@ export class ExtensionsScanner extends Disposable {
return this._devSystemExtensionsPath;
}
private async readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IMetadata | null; }> {
private async readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IStoredMetadata | null; }> {
const promises = [
fs.promises.readFile(path.join(extensionPath, 'package.json'), 'utf8')
.then(raw => this.parseManifest(raw)),

View File

@@ -0,0 +1,143 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { DidInstallExtensionEvent, DidUninstallExtensionEvent, IExtensionManagementService, ILocalExtension, InstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Emitter, Event } from 'vs/base/common/event';
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { FileChangeType, FileSystemProviderCapabilities, IFileChange, IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ExtUri } from 'vs/base/common/resources';
import { ILogService } from 'vs/platform/log/common/log';
export class ExtensionsWatcher extends Disposable {
private readonly _onDidChangeExtensionsByAnotherSource = this._register(new Emitter<{ added: ILocalExtension[], removed: IExtensionIdentifier[] }>());
readonly onDidChangeExtensionsByAnotherSource = this._onDidChangeExtensionsByAnotherSource.event;
private startTimestamp = 0;
private installingExtensions: IExtensionIdentifier[] = [];
private installedExtensions: IExtensionIdentifier[] | undefined;
constructor(
private readonly extensionsManagementService: IExtensionManagementService,
@IFileService fileService: IFileService,
@INativeEnvironmentService environmentService: INativeEnvironmentService,
@ILogService private readonly logService: ILogService,
) {
super();
this.extensionsManagementService.getInstalled(ExtensionType.User).then(extensions => {
this.installedExtensions = extensions.map(e => e.identifier);
this.startTimestamp = Date.now();
});
this._register(extensionsManagementService.onInstallExtension(e => this.onInstallExtension(e)));
this._register(extensionsManagementService.onDidInstallExtension(e => this.onDidInstallExtension(e)));
this._register(extensionsManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e)));
const extensionsResource = URI.file(environmentService.extensionsPath);
const extUri = new ExtUri(resource => !fileService.hasCapability(resource, FileSystemProviderCapabilities.PathCaseSensitive));
this._register(fileService.watch(extensionsResource));
this._register(Event.filter(fileService.onDidFilesChange, e => e.changes.some(change => this.doesChangeAffects(change, extensionsResource, extUri)))(() => this.onDidChange()));
}
private doesChangeAffects(change: IFileChange, extensionsResource: URI, extUri: ExtUri): boolean {
// Is not immediate child of extensions resource
if (!extUri.isEqual(extUri.dirname(change.resource), extensionsResource)) {
return false;
}
// .obsolete file changed
if (extUri.isEqual(change.resource, extUri.joinPath(extensionsResource, '.obsolete'))) {
return true;
}
// Only interested in added/deleted changes
if (change.type !== FileChangeType.ADDED && change.type !== FileChangeType.DELETED) {
return false;
}
// Ingore changes to files starting with `.`
if (extUri.basename(change.resource).startsWith('.')) {
return false;
}
return true;
}
private onInstallExtension(e: InstallExtensionEvent): void {
this.addInstallingExtension(e.identifier);
}
private onDidInstallExtension(e: DidInstallExtensionEvent): void {
this.removeInstallingExtension(e.identifier);
if (!e.error) {
this.addInstalledExtension(e.identifier);
}
}
private onDidUninstallExtension(e: DidUninstallExtensionEvent): void {
if (!e.error) {
this.removeInstalledExtension(e.identifier);
}
}
private addInstallingExtension(extension: IExtensionIdentifier) {
this.removeInstallingExtension(extension);
this.installingExtensions.push(extension);
}
private removeInstallingExtension(identifier: IExtensionIdentifier) {
this.installingExtensions = this.installingExtensions.filter(e => !areSameExtensions(e, identifier));
}
private addInstalledExtension(extension: IExtensionIdentifier): void {
if (this.installedExtensions) {
this.removeInstalledExtension(extension);
this.installedExtensions.push(extension);
}
}
private removeInstalledExtension(identifier: IExtensionIdentifier): void {
if (this.installedExtensions) {
this.installedExtensions = this.installedExtensions.filter(e => !areSameExtensions(e, identifier));
}
}
private async onDidChange(): Promise<void> {
if (this.installedExtensions) {
const extensions = await this.extensionsManagementService.getInstalled(ExtensionType.User);
const added = extensions.filter(e => {
if ([...this.installingExtensions, ...this.installedExtensions!].some(identifier => areSameExtensions(identifier, e.identifier))) {
return false;
}
if (e.installedTimestamp && e.installedTimestamp > this.startTimestamp) {
this.logService.info('Detected extension installed from another source', e.identifier.id);
return true;
} else {
this.logService.info('Ignored extension installed by another source because of invalid timestamp', e.identifier.id);
return false;
}
});
const removed = this.installedExtensions.filter(identifier => {
// Extension being installed
if (this.installingExtensions.some(installingExtension => areSameExtensions(installingExtension, identifier))) {
return false;
}
if (extensions.every(e => !areSameExtensions(e.identifier, identifier))) {
this.logService.info('Detected extension removed from another source', identifier.id);
return true;
}
return false;
});
this.installedExtensions = extensions.map(e => e.identifier);
if (added.length || removed.length) {
this._onDidChangeExtensionsByAnotherSource.fire({ added, removed });
}
}
}
}