Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -5,11 +5,10 @@
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { distinct, coalesce } from 'vs/base/common/arrays';
import Event, { Emitter } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getIdFromLocalExtensionId, areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -24,7 +23,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
private disposables: IDisposable[] = [];
private _onEnablementChanged = new Emitter<IExtensionIdentifier>();
public onEnablementChanged: Event<IExtensionIdentifier> = this._onEnablementChanged.event;
public readonly onEnablementChanged: Event<IExtensionIdentifier> = this._onEnablementChanged.event;
constructor(
@IStorageService private storageService: IStorageService,
@@ -58,10 +57,11 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.as(result);
}
getEnablementState(identifier: IExtensionIdentifier): EnablementState {
if (this.environmentService.disableExtensions) {
getEnablementState(extension: ILocalExtension): EnablementState {
if (this.environmentService.disableExtensions && extension.type === LocalExtensionType.User) {
return EnablementState.Disabled;
}
const identifier = this._getIdentifier(extension);
if (this.hasWorkspace) {
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceEnabled;
@@ -78,18 +78,24 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
}
canChangeEnablement(extension: ILocalExtension): boolean {
return !this.environmentService.disableExtensions && !(extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length);
if (extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) {
return false;
}
if (extension.type === LocalExtensionType.User && this.environmentService.disableExtensions) {
return false;
}
return true;
}
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): TPromise<boolean> {
let identifier;
let identifier: IExtensionIdentifier;
if (isIExtensionIdentifier(arg)) {
identifier = arg;
} else {
if (!this.canChangeEnablement(arg)) {
return TPromise.wrap(false);
}
identifier = { id: getGalleryExtensionIdFromLocal(arg), uuid: arg.identifier.uuid };
identifier = this._getIdentifier(arg);
}
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
@@ -97,7 +103,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
}
const currentState = this.getEnablementState(identifier);
const currentState = this._getEnablementState(identifier);
if (currentState === newState) {
return TPromise.as(false);
@@ -123,16 +129,29 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.as(true);
}
isEnabled(identifier: IExtensionIdentifier): boolean {
const enablementState = this.getEnablementState(identifier);
isEnabled(extension: ILocalExtension): boolean {
const enablementState = this.getEnablementState(extension);
return enablementState === EnablementState.WorkspaceEnabled || enablementState === EnablementState.Enabled;
}
migrateToIdentifiers(installed: IExtensionIdentifier[]): void {
this._migrateDisabledExtensions(installed, StorageScope.GLOBAL);
private _getEnablementState(identifier: IExtensionIdentifier): EnablementState {
if (this.hasWorkspace) {
this._migrateDisabledExtensions(installed, StorageScope.WORKSPACE);
if (this._getEnabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceEnabled;
}
if (this._getDisabledExtensions(StorageScope.WORKSPACE).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.WorkspaceDisabled;
}
}
if (this._getDisabledExtensions(StorageScope.GLOBAL).filter(e => areSameExtensions(e, identifier))[0]) {
return EnablementState.Disabled;
}
return EnablementState.Enabled;
}
private _getIdentifier(extension: ILocalExtension): IExtensionIdentifier {
return { id: getGalleryExtensionIdFromLocal(extension), uuid: extension.identifier.uuid };
}
private _enableExtension(identifier: IExtensionIdentifier): void {
@@ -227,8 +246,8 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return this._getExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension, fireEvent);
private _setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
this._setExtensions(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions, scope, extension);
}
private _getExtensions(storageId: string, scope: StorageScope): IExtensionIdentifier[] {
@@ -239,30 +258,12 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return value ? JSON.parse(value) : [];
}
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
private _setExtensions(storageId: string, extensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier): void {
if (extensions.length) {
this.storageService.store(storageId, JSON.stringify(extensions.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid }))), scope);
} else {
this.storageService.remove(storageId, scope);
}
if (fireEvent) {
this._onEnablementChanged.fire(extension);
}
}
private _migrateDisabledExtensions(installedExtensions: IExtensionIdentifier[], scope: StorageScope): void {
const oldValue = this.storageService.get('extensions/disabled', scope, '');
if (oldValue) {
const extensionIdentifiers = coalesce(distinct(oldValue.split(',')).map(id => {
id = adoptToGalleryExtensionId(id);
const matched = installedExtensions.filter(installed => areSameExtensions({ id }, { id: installed.id }))[0];
return matched ? { id: matched.id, uuid: matched.uuid } : null;
}));
if (extensionIdentifiers.length) {
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, JSON.stringify(extensionIdentifiers), scope);
}
}
this.storageService.remove('extensions/disabled', scope);
}
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {

View File

@@ -7,7 +7,7 @@
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IPager } from 'vs/base/common/paging';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { ILocalization } from 'vs/platform/localizations/common/localizations';
@@ -118,6 +118,12 @@ export interface IExtensionManifest {
activationEvents?: string[];
extensionDependencies?: string[];
contributes?: IExtensionContributions;
repository?: {
url: string;
};
bugs?: {
url: string;
};
}
export interface IGalleryExtensionProperties {
@@ -149,6 +155,12 @@ export function isIExtensionIdentifier(thing: any): thing is IExtensionIdentifie
&& (!thing.uuid || typeof thing.uuid === 'string');
}
/* __GDPR__FRAGMENT__
"ExtensionIdentifier" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"uuid": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
export interface IExtensionIdentifier {
id: string;
uuid?: string;
@@ -274,10 +286,10 @@ export interface IExtensionManagementService {
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
install(zipPath: string): TPromise<void>;
installFromGallery(extension: IGalleryExtension): TPromise<void>;
install(zipPath: string): TPromise<ILocalExtension>;
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension>;
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
reinstall(extension: ILocalExtension): TPromise<void>;
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension>;
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
@@ -311,7 +323,7 @@ export interface IExtensionEnablementService {
/**
* Returns the enablement state for the given extension
*/
getEnablementState(identifier: IExtensionIdentifier): EnablementState;
getEnablementState(extension: ILocalExtension): EnablementState;
/**
* Returns `true` if the enablement can be changed.
@@ -321,7 +333,7 @@ export interface IExtensionEnablementService {
/**
* Returns `true` if the given extension identifier is enabled.
*/
isEnabled(identifier: IExtensionIdentifier): boolean;
isEnabled(extension: ILocalExtension): boolean;
/**
* Enable or disable the given extension.
@@ -333,12 +345,6 @@ export interface IExtensionEnablementService {
* Throws error if enablement is requested for workspace and there is no workspace
*/
setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>;
/**
* TODO: @Sandy. Use setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>. Use one model for extension management and runtime
*/
setEnablement(identifier: IExtensionIdentifier, state: EnablementState): TPromise<boolean>;
migrateToIdentifiers(installed: IExtensionIdentifier[]): void;
}
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('extensionTipsService');

View File

@@ -8,16 +8,17 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from './extensionManagement';
import Event, { buffer } from 'vs/base/common/event';
import { Event, buffer } from 'vs/base/common/event';
export interface IExtensionManagementChannel extends IChannel {
call(command: 'event:onInstallExtension'): TPromise<void>;
call(command: 'event:onDidInstallExtension'): TPromise<void>;
call(command: 'event:onUninstallExtension'): TPromise<void>;
call(command: 'event:onDidUninstallExtension'): TPromise<void>;
call(command: 'install', path: string): TPromise<void>;
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<void>;
call(command: 'install', path: string): TPromise<ILocalExtension>;
call(command: 'installFromGallery', extension: IGalleryExtension): TPromise<ILocalExtension>;
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
call(command: 'reinstallFromGallery', args: [ILocalExtension]): TPromise<ILocalExtension>;
call(command: 'getInstalled'): TPromise<ILocalExtension[]>;
call(command: 'getExtensionsReport'): TPromise<IReportedExtension[]>;
call(command: string, arg?: any): TPromise<any>;
@@ -46,7 +47,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
case 'install': return this.service.install(arg);
case 'installFromGallery': return this.service.installFromGallery(arg[0]);
case 'uninstall': return this.service.uninstall(arg[0], arg[1]);
case 'reinstall': return this.service.reinstall(arg[0]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(arg[0]);
case 'getInstalled': return this.service.getInstalled(arg);
case 'updateMetadata': return this.service.updateMetadata(arg[0], arg[1]);
case 'getExtensionsReport': return this.service.getExtensionsReport();
@@ -73,11 +74,11 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
private _onDidUninstallExtension = eventFromCall<DidUninstallExtensionEvent>(this.channel, 'event:onDidUninstallExtension');
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this._onDidUninstallExtension; }
install(zipPath: string): TPromise<void> {
install(zipPath: string): TPromise<ILocalExtension> {
return this.channel.call('install', zipPath);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
return this.channel.call('installFromGallery', [extension]);
}
@@ -85,8 +86,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
return this.channel.call('uninstall', [extension, force]);
}
reinstall(extension: ILocalExtension): TPromise<void> {
return this.channel.call('reinstall', [extension]);
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension> {
return this.channel.call('reinstallFromGallery', [extension]);
}
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {

View File

@@ -82,10 +82,10 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"name": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"galleryId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"publisherName": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" },
"publisherDisplayName": { "classification": "PublicPersonalData", "purpose": "FeatureInsight" },
"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData2}"
]

View File

@@ -18,7 +18,7 @@ import { IPager } from 'vs/base/common/paging';
import { IRequestOptions, IRequestContext, download, asJson, asText } from 'vs/base/node/request';
import pkg from 'vs/platform/node/package';
import product from 'vs/platform/node/product';
import { isVersionValid } from 'vs/platform/extensions/node/extensionValidator';
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { readFile } from 'vs/base/node/pfs';
import { writeFileAndFlushSync } from 'vs/base/node/extfs';
@@ -324,7 +324,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
},
/* __GDPR__FRAGMENT__
"GalleryExtensionTelemetryData2" : {
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"searchText": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
@@ -586,7 +586,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
@@ -622,7 +622,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension> {
if (extension.properties.engine && this.isEngineValid(extension.properties.engine)) {
if (extension.properties.engine && isEngineValid(extension.properties.engine)) {
return TPromise.wrap(extension);
}
@@ -738,7 +738,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
/* __GDPR__
"galleryService:requestError" : {
"url" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cdn": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
@@ -786,7 +786,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
if (!engine) {
return null;
}
if (this.isEngineValid(engine)) {
if (isEngineValid(engine)) {
return TPromise.wrap(version);
}
}
@@ -807,7 +807,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.then(manifest => {
const engine = manifest.engines.vscode;
if (!this.isEngineValid(engine)) {
if (!isEngineValid(engine)) {
return this.getLastValidExtensionVersionReccursively(extension, versions.slice(1));
}
@@ -817,11 +817,6 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private isEngineValid(engine: string): boolean {
// TODO@joao: discuss with alex '*' doesn't seem to be a valid engine version
return engine === '*' || isVersionValid(pkg.version, engine);
}
private static hasExtensionByName(extensions: IGalleryExtension[], name: string): boolean {
for (const extension of extensions) {
if (`${extension.publisher}.${extension.name}` === name) {

View File

@@ -5,14 +5,14 @@
'use strict';
import nls = require('vs/nls');
import * as nls from 'vs/nls';
import * as path from 'path';
import * as pfs from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import { assign } from 'vs/base/common/objects';
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { flatten, distinct, coalesce } from 'vs/base/common/arrays';
import { extract, buffer } from 'vs/base/node/zip';
import { flatten, distinct } from 'vs/base/common/arrays';
import { extract, buffer, ExtractError } from 'vs/base/node/zip';
import { TPromise } from 'vs/base/common/winjs.base';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
@@ -22,21 +22,23 @@ import {
IExtensionIdentifier,
IReportedExtension
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getLocalExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { localizeManifest } from '../common/extensionNls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Limiter } from 'vs/base/common/async';
import Event, { Emitter } from 'vs/base/common/event';
import { Limiter, always } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import * as semver from 'semver';
import URI from 'vs/base/common/uri';
import pkg from 'vs/platform/node/package';
import { isMacintosh } from 'vs/base/common/platform';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions'));
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
@@ -48,10 +50,9 @@ const INSTALL_ERROR_VALIDATING = 'validating';
const INSTALL_ERROR_GALLERY = 'gallery';
const INSTALL_ERROR_LOCAL = 'local';
const INSTALL_ERROR_EXTRACTING = 'extracting';
const INSTALL_ERROR_RENAMING = 'renaming';
const INSTALL_ERROR_DELETING = 'deleting';
const INSTALL_ERROR_READING_EXTENSION_FROM_DISK = 'readingExtension';
const INSTALL_ERROR_SAVING_METADATA = 'savingMetadata';
const INSTALL_ERROR_UNKNOWN = 'unknown';
const ERROR_UNKNOWN = 'unknown';
export class ExtensionManagementError extends Error {
constructor(message: string, readonly code: string) {
@@ -101,6 +102,11 @@ interface InstallableExtension {
metadata?: IGalleryMetadata;
}
enum Operation {
Install = 1,
Update
}
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
_serviceBrand: any;
@@ -110,6 +116,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
private uninstalledFileLimiter: Limiter<void>;
private reportedExtensions: TPromise<IReportedExtension[]> | undefined;
private lastReportTimestamp = 0;
private readonly installationStartTime: Map<string, number> = new Map<string, number>();
private readonly installingExtensions: Map<string, TPromise<ILocalExtension>> = new Map<string, TPromise<ILocalExtension>>();
private readonly manifestCache: ExtensionsManifestCache;
private readonly extensionLifecycle: ExtensionsLifecycle;
@@ -128,9 +135,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@IChoiceService private choiceService: IChoiceService,
@IDialogService private dialogService: IDialogService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@ILogService private logService: ILogService
@ILogService private logService: ILogService,
@ITelemetryService private telemetryService: ITelemetryService,
) {
super();
this.extensionsPath = environmentService.extensionsPath;
@@ -141,13 +149,17 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.extensionLifecycle = this._register(new ExtensionsLifecycle(this.logService));
}
install(zipPath: string): TPromise<void> {
install(zipPath: string): TPromise<ILocalExtension> {
zipPath = path.resolve(zipPath);
return validateLocalExtension(zipPath)
.then(manifest => {
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
return this.unsetUninstalledAndRemove(identifier.id)
// {{SQL CARBON EDIT - Remove VS Code version check}}
// if (manifest.engines && manifest.engines.vscode && !isEngineValid(manifest.engines.vscode)) {
// return TPromise.wrapError<ILocalExtension>(new Error(nls.localize('incompatible', "Unable to install Extension '{0}' as it is not compatible with Code '{1}'.", identifier.id, pkg.version)));
// }
return this.removeIfExists(identifier.id)
.then(
() => this.checkOutdated(manifest)
.then(validated => {
@@ -175,18 +187,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private unsetUninstalledAndRemove(id: string): TPromise<void> {
return this.isUninstalled(id)
.then(isUninstalled => {
if (isUninstalled) {
this.logService.trace('Removing the extension:', id);
const extensionPath = path.join(this.extensionsPath, id);
return pfs.rimraf(extensionPath)
.then(() => this.unsetUninstalled(id))
.then(() => this.logService.info('Removed the extension:', id));
}
return null;
});
private removeIfExists(id: string): TPromise<void> {
return this.getInstalled(LocalExtensionType.User)
.then(installed => installed.filter(i => i.identifier.id === id)[0])
.then(existing => existing ? this.removeExtension(existing, 'existing') : null);
}
private checkOutdated(manifest: IExtensionManifest): TPromise<boolean> {
@@ -196,11 +200,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0];
if (newer) {
const message = nls.localize('installingOutdatedExtension', "A newer version of this extension is already installed. Would you like to override this with the older version?");
const options = [
const buttons = [
nls.localize('override', "Override"),
nls.localize('cancel', "Cancel")
];
return this.choiceService.choose(Severity.Info, message, options, 1, true)
return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
.then<boolean>(value => {
if (value === 0) {
return this.uninstall(newer, true).then(() => true);
@@ -212,58 +216,65 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<void> {
private installFromZipPath(identifier: IExtensionIdentifier, zipPath: string, metadata: IGalleryMetadata, manifest: IExtensionManifest): TPromise<ILocalExtension> {
return this.installExtension({ zipPath, id: identifier.id, metadata })
.then(local => {
if (this.galleryService.isEnabled() && local.manifest.extensionDependencies && local.manifest.extensionDependencies.length) {
return this.getDependenciesToInstall(local.manifest.extensionDependencies)
.then(dependenciesToInstall => this.downloadAndInstallExtensions(metadata ? dependenciesToInstall.filter(d => d.identifier.uuid !== metadata.id) : dependenciesToInstall))
.then(() => local, error => {
this.uninstallExtension(local);
this.setUninstalled(local);
return TPromise.wrapError(new Error(nls.localize('errorInstallingDependencies', "Error while installing dependencies. {0}", error instanceof Error ? error.message : error)));
});
}
return local;
})
.then(
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
local => { this._onDidInstallExtension.fire({ identifier, zipPath, local }); return local; },
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
installFromGallery(extension: IGalleryExtension): TPromise<ILocalExtension> {
this.onInstallExtensions([extension]);
return this.collectExtensionsToInstall(extension)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
return this.downloadAndInstallExtensions(extensionsToInstall)
.then(
locals => this.onDidInstallExtensions(extensionsToInstall, locals, []),
errors => this.onDidInstallExtensions(extensionsToInstall, [], errors));
},
error => this.onDidInstallExtensions([extension], [], [error]));
return this.getInstalled(LocalExtensionType.User)
.then(installed => this.collectExtensionsToInstall(extension)
.then(
extensionsToInstall => {
if (extensionsToInstall.length > 1) {
this.onInstallExtensions(extensionsToInstall.slice(1));
}
const operataions: Operation[] = extensionsToInstall.map(e => this.getOperation(e, installed));
return this.downloadAndInstallExtensions(extensionsToInstall)
.then(
locals => this.onDidInstallExtensions(extensionsToInstall, locals, operataions, [])
.then(() => locals.filter(l => areSameExtensions({ id: getGalleryExtensionIdFromLocal(l), uuid: l.identifier.uuid }, extension.identifier)[0])),
errors => this.onDidInstallExtensions(extensionsToInstall, [], operataions, errors));
},
error => this.onDidInstallExtensions([extension], [], [this.getOperation(extension, installed)], [error])));
}
reinstall(extension: ILocalExtension): TPromise<void> {
reinstallFromGallery(extension: ILocalExtension): TPromise<ILocalExtension> {
if (!this.galleryService.isEnabled()) {
return TPromise.wrapError(new Error(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled")));
}
return this.findGalleryExtension(extension)
.then(galleryExtension => {
if (galleryExtension) {
return this.uninstallExtension(extension)
return this.setUninstalled(extension)
.then(() => this.removeUninstalledExtension(extension)
.then(
() => this.installFromGallery(galleryExtension),
e => TPromise.wrapError(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
() => this.installFromGallery(galleryExtension),
e => TPromise.wrapError(new Error(nls.localize('removeError', "Error while removing the extension: {0}. Please Quit and Start VS Code before trying again.", toErrorMessage(e))))));
}
return TPromise.wrapError(new Error(nls.localize('Not Market place extension', "Only Market place Extensions can be reinstalled")));
return TPromise.wrapError(new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")));
});
}
private getOperation(extensionToInstall: IGalleryExtension, installed: ILocalExtension[]): Operation {
return installed.some(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, extensionToInstall.identifier)) ? Operation.Update : Operation.Install;
}
private collectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
return this.galleryService.loadCompatibleVersion(extension)
.then(compatible => {
@@ -273,10 +284,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
return this.getDependenciesToInstall(compatible.properties.dependencies)
.then(
dependenciesToInstall => ([compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)]),
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
dependenciesToInstall => ([compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)]),
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
},
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
error => TPromise.wrapError<IGalleryExtension[]>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
}
private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
@@ -298,8 +309,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
.then(extension => this.downloadInstallableExtension(extension))
.then(installableExtension => this.installExtension(installableExtension))
.then(
local => { this.installingExtensions.delete(extension.identifier.id); return local; },
e => { this.installingExtensions.delete(extension.identifier.id); return TPromise.wrapError(e); }
local => { this.installingExtensions.delete(extension.identifier.id); return local; },
e => { this.installingExtensions.delete(extension.identifier.id); return TPromise.wrapError(e); }
);
this.installingExtensions.set(extension.identifier.id, installingExtension);
@@ -316,36 +327,37 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.galleryService.loadCompatibleVersion(extension)
.then(
compatible => {
if (compatible) {
this.logService.trace('Started downloading extension:', extension.name);
return this.galleryService.download(extension)
.then(
zipPath => {
this.logService.info('Downloaded extension:', extension.name);
return validateLocalExtension(zipPath)
.then(
manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
);
},
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
} else {
return TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
},
error => TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
compatible => {
if (compatible) {
this.logService.trace('Started downloading extension:', extension.name);
return this.galleryService.download(extension)
.then(
zipPath => {
this.logService.info('Downloaded extension:', extension.name);
return validateLocalExtension(zipPath)
.then(
manifest => (<InstallableExtension>{ zipPath, id: getLocalExtensionIdFromManifest(manifest), metadata }),
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING))
);
},
error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING)));
} else {
return TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE));
}
},
error => TPromise.wrapError<InstallableExtension>(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY)));
}
private onInstallExtensions(extensions: IGalleryExtension[]): void {
for (const extension of extensions) {
this.logService.info('Installing extension:', extension.name);
this.installationStartTime.set(extension.identifier.id, new Date().getTime());
const id = getLocalExtensionIdFromGallery(extension, extension.version);
this._onInstallExtension.fire({ identifier: { id, uuid: extension.identifier.uuid }, gallery: extension });
}
}
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], errors: Error[]): TPromise<any> {
private onDidInstallExtensions(extensions: IGalleryExtension[], locals: ILocalExtension[], operations: Operation[], errors: Error[]): TPromise<any> {
extensions.forEach((gallery, index) => {
const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid };
const local = locals[index];
@@ -354,10 +366,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.logService.info(`Extensions installed successfully:`, gallery.identifier.id);
this._onDidInstallExtension.fire({ identifier, gallery, local });
} else {
const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : INSTALL_ERROR_UNKNOWN;
const errorCode = error && (<ExtensionManagementError>error).code ? (<ExtensionManagementError>error).code : ERROR_UNKNOWN;
this.logService.error(`Failed to install extension:`, gallery.identifier.id, error ? error.message : errorCode);
this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode });
}
const startTime = this.installationStartTime.get(gallery.identifier.id);
this.reportTelemetry(operations[index] === Operation.Install ? 'extensionGallery:install' : 'extensionGallery:update', getGalleryExtensionTelemetryData(gallery), startTime ? new Date().getTime() - startTime : void 0, error);
this.installationStartTime.delete(gallery.identifier.id);
});
return errors.length ? TPromise.wrapError(this.joinErrors(errors)) : TPromise.as(null);
}
@@ -383,18 +398,18 @@ export class ExtensionManagementService extends Disposable implements IExtension
private installExtension(installableExtension: InstallableExtension): TPromise<ILocalExtension> {
return this.unsetUninstalledAndGetLocal(installableExtension.id)
.then(
local => {
if (local) {
return local;
}
return this.extractAndInstall(installableExtension);
},
e => {
if (isMacintosh) {
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
}
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
});
local => {
if (local) {
return local;
}
return this.extractAndInstall(installableExtension);
},
e => {
if (isMacintosh) {
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
}
return TPromise.wrapError<ILocalExtension>(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED));
});
}
private unsetUninstalledAndGetLocal(id: string): TPromise<ILocalExtension> {
@@ -415,43 +430,53 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
const tempPath = path.join(this.extensionsPath, `.${id}`);
const extensionPath = path.join(this.extensionsPath, id);
return pfs.rimraf(extensionPath)
return this.extractAndRename(id, zipPath, tempPath, extensionPath)
.then(() => {
this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionPath}`);
return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true })
.then(
() => {
this.logService.info(`Extracted extension to ${extensionPath}:`, id);
return this.completeInstall(id, extensionPath, metadata);
},
e => TPromise.wrapError(new ExtensionManagementError(e.message, INSTALL_ERROR_EXTRACTING)))
.then(null, e => {
this.logService.info('Deleting the extracted extension', id);
return pfs.rimraf(extensionPath).then(() => TPromise.wrapError(e), () => TPromise.wrapError(e));
});
}, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
this.logService.info('Installation completed.', id);
return this.scanExtension(id, this.extensionsPath, LocalExtensionType.User);
})
.then(local => {
if (metadata) {
local.metadata = metadata;
return this.saveMetadataForLocalExtension(local);
}
return local;
});
}
private completeInstall(id: string, extensionPath: string, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
return TPromise.join([readManifest(extensionPath), pfs.readdir(extensionPath)])
.then(null, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_READING_EXTENSION_FROM_DISK)))
.then(([{ manifest }, children]) => {
const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0];
const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null;
const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0];
const changelogUrl = changelog ? URI.file(path.join(extensionPath, changelog)).toString() : null;
const type = LocalExtensionType.User;
const identifier = { id, uuid: metadata ? metadata.id : null };
private extractAndRename(id: string, zipPath: string, extractPath: string, renamePath: string): TPromise<void> {
return this.extract(id, zipPath, extractPath)
.then(() => this.rename(id, extractPath, renamePath, Date.now() + (30 * 1000) /* Retry for 30 seconds */)
.then(
() => this.logService.info('Renamed to', renamePath),
e => {
this.logService.info('Rename failed. Deleting from extracted location', extractPath);
return always(pfs.rimraf(extractPath), () => null).then(() => TPromise.wrapError(e));
}));
}
const local: ILocalExtension = { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
private extract(id: string, zipPath: string, extractPath: string): TPromise<void> {
this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`);
return pfs.rimraf(extractPath)
.then(
() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService)
.then(
() => this.logService.info(`Extracted extension to ${extractPath}:`, id),
e => always(pfs.rimraf(extractPath), () => null)
.then(() => TPromise.wrapError(new ExtensionManagementError(e.message, e instanceof ExtractError ? e.type : INSTALL_ERROR_EXTRACTING)))),
e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
}
this.logService.trace(`Updating metadata of the extension:`, id);
return this.saveMetadataForLocalExtension(local)
.then(() => {
this.logService.info(`Updated metadata of the extension:`, id);
return local;
}, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_SAVING_METADATA)));
private rename(id: string, extractPath: string, renamePath: string, retryUntil: number): TPromise<void> {
return pfs.rename(extractPath, renamePath)
.then(null, error => {
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
this.logService.info(`Failed renaming ${extractPath} to ${renamePath} with 'EPERM' error. Trying again...`);
return this.rename(id, extractPath, renamePath, retryUntil);
}
return TPromise.wrapError(new ExtensionManagementError(error.message || nls.localize('renameError', "Unknown error while renaming {0} to {1}", extractPath, renamePath), error.code || INSTALL_ERROR_RENAMING));
});
}
@@ -459,7 +484,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.getInstalled(LocalExtensionType.User)
.then(installed =>
TPromise.join(installed.filter(local => extensions.some(galleryExtension => local.identifier.id === getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version))) // Only check id (pub.name-version) because we want to rollback the exact version
.map(local => this.uninstallExtension(local))))
.map(local => this.setUninstalled(local))))
.then(() => null, () => null);
}
@@ -529,10 +554,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.preUninstallExtension(extension)
.then(() => this.hasDependencies(extension, installed) ? this.promptForDependenciesAndUninstall(extension, installed, force) : this.promptAndUninstall(extension, installed, force))
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
return TPromise.wrapError(error);
});
error => {
this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
return TPromise.wrapError(error);
});
}
private hasDependencies(extension: ILocalExtension, installed: ILocalExtension[]): boolean {
@@ -549,13 +574,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
const message = nls.localize('uninstallDependeciesConfirmation', "Would you like to uninstall '{0}' only or its dependencies also?", extension.manifest.displayName || extension.manifest.name);
const options = [
nls.localize('uninstallOnly', "Only"),
nls.localize('uninstallAll', "All"),
const buttons = [
nls.localize('uninstallOnly', "Extension Only"),
nls.localize('uninstallAll', "Uninstall All"),
nls.localize('cancel', "Cancel")
];
this.logService.info('Requesting for confirmation to uninstall extension with dependencies', extension.identifier.id);
return this.choiceService.choose(Severity.Info, message, options, 2, true)
return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 2 })
.then<void>(value => {
if (value === 0) {
return this.uninstallWithDependencies(extension, [], installed);
@@ -575,12 +600,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
const message = nls.localize('uninstallConfirmation', "Are you sure you want to uninstall '{0}'?", extension.manifest.displayName || extension.manifest.name);
const options = [
const buttons = [
nls.localize('ok', "OK"),
nls.localize('cancel', "Cancel")
];
this.logService.info('Requesting for confirmation to uninstall extension', extension.identifier.id);
return this.choiceService.choose(Severity.Info, message, options, 1, true)
return this.dialogService.show(Severity.Info, message, buttons, { cancelId: 1 })
.then<void>(value => {
if (value === 0) {
return this.uninstallWithDependencies(extension, [], installed);
@@ -649,10 +674,10 @@ export class ExtensionManagementService extends Disposable implements IExtension
return this.preUninstallExtension(extension)
.then(() => this.uninstallExtension(extension))
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
return TPromise.wrapError(error);
});
error => {
this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
return TPromise.wrapError(error);
});
}
private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
@@ -665,12 +690,14 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
private uninstallExtension(local: ILocalExtension): TPromise<void> {
return this.setUninstalled(local.identifier.id);
// Set all versions of the extension as uninstalled
return this.scanUserExtensions(false)
.then(userExtensions => this.setUninstalled(...userExtensions.filter(u => areSameExtensions({ id: getGalleryExtensionIdFromLocal(u), uuid: u.identifier.uuid }, { id: getGalleryExtensionIdFromLocal(local), uuid: local.identifier.uuid }))));
}
private async postUninstallExtension(extension: ILocalExtension, error?: string): TPromise<void> {
private async postUninstallExtension(extension: ILocalExtension, error?: Error): TPromise<void> {
if (error) {
this.logService.error('Failed to uninstall extension:', extension.identifier.id, error);
this.logService.error('Failed to uninstall extension:', extension.identifier.id, error.message);
} else {
this.logService.info('Successfully uninstalled extension:', extension.identifier.id);
// only report if extension has a mapped gallery extension. UUID identifies the gallery extension.
@@ -678,7 +705,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
}
}
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error });
this.reportTelemetry('extensionGallery:uninstall', getLocalExtensionTelemetryData(extension), void 0, error);
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error: errorcode });
}
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
@@ -721,11 +750,14 @@ export class ExtensionManagementService extends Disposable implements IExtension
private scanExtensions(root: string, type: LocalExtensionType): TPromise<ILocalExtension[]> {
const limiter = new Limiter(10);
return pfs.readdir(root)
.then(extensionsFolders => TPromise.join(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => coalesce(extensions));
.then(extensionsFolders => TPromise.join<ILocalExtension>(extensionsFolders.map(extensionFolder => limiter.queue(() => this.scanExtension(extensionFolder, root, type)))))
.then(extensions => extensions.filter(e => e && e.identifier));
}
private scanExtension(folderName: string, root: string, type: LocalExtensionType): TPromise<ILocalExtension> {
if (type === LocalExtensionType.User && folderName.indexOf('.') === 0) { // Do not consider user exension folder starting with `.`
return TPromise.as(null);
}
const extensionPath = path.join(root, folderName);
return pfs.readdir(extensionPath)
.then(children => readManifest(extensionPath)
@@ -798,7 +830,8 @@ export class ExtensionManagementService extends Disposable implements IExtension
});
}
private setUninstalled(...ids: string[]): TPromise<void> {
private setUninstalled(...extensions: ILocalExtension[]): TPromise<void> {
const ids = extensions.map(e => e.identifier.id);
return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {})));
}
@@ -852,6 +885,31 @@ export class ExtensionManagementService extends Disposable implements IExtension
return [];
});
}
private reportTelemetry(eventName: string, extensionData: any, duration: number, error?: Error): void {
const errorcode = error ? error instanceof ExtensionManagementError ? error.code : ERROR_UNKNOWN : void 0;
/* __GDPR__
"extensionGallery:install" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
/* __GDPR__
"extensionGallery:uninstall" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode }));
}
}
export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string {
@@ -860,4 +918,4 @@ export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, ver
export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): string {
return getLocalExtensionId(getGalleryExtensionId(manifest.publisher, manifest.name), manifest.version);
}
}

View File

@@ -6,7 +6,7 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
@@ -98,13 +98,13 @@ suite('ExtensionEnablementService Test', () => {
test('test state of globally disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
});
test('test state of globally enabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test disable an extension for workspace', () => {
@@ -126,59 +126,59 @@ suite('ExtensionEnablementService Test', () => {
test('test state of workspace disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
});
test('test state of workspace and globally disabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
});
test('test state of workspace enabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceEnabled));
});
test('test state of globally disabled and workspace enabled extension', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceEnabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceEnabled));
});
test('test state of an extension when disabled for workspace from workspace enabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.WorkspaceDisabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.WorkspaceDisabled));
});
test('test state of an extension when disabled globally from workspace enabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
});
test('test state of an extension when disabled globally from workspace disabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Disabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Disabled));
});
test('test state of an extension when enabled globally from workspace enabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceEnabled))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test state of an extension when enabled globally from workspace disabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.equal(testObject.getEnablementState({ id: 'pub.a' }), EnablementState.Enabled));
.then(() => assert.equal(testObject.getEnablementState(aLocalExtension('pub.a')), EnablementState.Enabled));
});
test('test disable an extension for workspace and then globally', () => {
@@ -223,11 +223,10 @@ suite('ExtensionEnablementService Test', () => {
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
});
test('test disable an extension for workspace when there is no workspace throws error', (done) => {
test('test disable an extension for workspace when there is no workspace throws error', () => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => assert.fail('should throw an error'), error => assert.ok(error))
.then(done, done);
.then(() => assert.fail('should throw an error'), error => assert.ok(error));
});
test('test enable an extension globally', () => {
@@ -237,26 +236,23 @@ suite('ExtensionEnablementService Test', () => {
.then(extensions => assert.deepEqual([], extensions));
});
test('test enable an extension globally return truthy promise', (done) => {
testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
test('test enable an extension globally return truthy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(value => assert.ok(value))
.then(done, done);
.then(value => assert.ok(value));
});
test('test enable an extension globally triggers change event', (done) => {
test('test enable an extension globally triggers change event', () => {
const target = sinon.spy();
testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })))
.then(done, done);
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a', uuid: void 0 })));
});
test('test enable an extension globally when already enabled return falsy promise', (done) => {
testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled)
.then(value => assert.ok(!value))
.then(done, done);
test('test enable an extension globally when already enabled return falsy promise', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Enabled)
.then(value => assert.ok(!value));
});
test('test enable an extension for workspace', () => {
@@ -311,23 +307,37 @@ suite('ExtensionEnablementService Test', () => {
test('test isEnabled return false extension is disabled globally', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled)
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
.then(() => assert.ok(!testObject.isEnabled(aLocalExtension('pub.a'))));
});
test('test isEnabled return false extension is disabled in workspace', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => assert.ok(!testObject.isEnabled({ id: 'pub.a' })));
.then(() => assert.ok(!testObject.isEnabled(aLocalExtension('pub.a'))));
});
test('test isEnabled return true extension is not disabled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.c'), EnablementState.Disabled))
.then(() => assert.ok(testObject.isEnabled({ id: 'pub.b' })));
.then(() => assert.ok(testObject.isEnabled(aLocalExtension('pub.b'))));
});
test('test canChangeEnablement return false for language packs', () => {
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a', { localizations: [{ languageId: 'gr', translations: [{ id: 'vscode', path: 'path' }] }] })), false);
});
test('test canChangeEnablement return false when extensions are disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
assert.equal(testObject.canChangeEnablement(aLocalExtension('pub.a')), false);
});
test('test canChangeEnablement return true for system extensions when extensions are disabled in environment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: true } as IEnvironmentService);
testObject = new TestExtensionEnablementService(instantiationService);
const extension = aLocalExtension('pub.a');
extension.type = LocalExtensionType.System;
assert.equal(testObject.canChangeEnablement(extension), true);
});
});
function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension {
@@ -338,6 +348,7 @@ function aLocalExtension(id: string, contributes?: IExtensionContributions): ILo
name,
publisher,
contributes
}
},
type: LocalExtensionType.User
});
}

View File

@@ -7,7 +7,7 @@
import * as assert from 'assert';
import * as os from 'os';
import extfs = require('vs/base/node/extfs');
import * as extfs from 'vs/base/node/extfs';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { parseArgs } from 'vs/platform/environment/node/argv';
import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices';
@@ -27,7 +27,7 @@ suite('Extension Gallery Service', () => {
extfs.del(marketplaceHome, os.tmpdir(), () => {
mkdirp(marketplaceHome).then(() => {
done();
});
}, error => done(error));
});
});
@@ -35,7 +35,7 @@ suite('Extension Gallery Service', () => {
extfs.del(marketplaceHome, os.tmpdir(), done);
});
test('marketplace machine id', done => {
test('marketplace machine id', () => {
const args = ['--user-data-dir', marketplaceHome];
const environmentService = new EnvironmentService(parseArgs(args), process.execPath);
@@ -44,8 +44,6 @@ suite('Extension Gallery Service', () => {
return resolveMarketplaceHeaders(environmentService).then(headers2 => {
assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']);
done();
});
});
});