Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, ILocalExtension, isIExtensionIdentifier, LocalExtensionType, DidInstallExtensionEvent, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getIdFromLocalExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
@@ -31,6 +30,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService
) {
extensionManagementService.onDidInstallExtension(this._onDidInstallExtension, this, this.disposables);
extensionManagementService.onDidUninstallExtension(this._onDidUninstallExtension, this, this.disposables);
}
@@ -102,26 +102,26 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return true;
}
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): TPromise<boolean> {
setEnablement(arg: ILocalExtension | IExtensionIdentifier, newState: EnablementState): Promise<boolean> {
let identifier: IExtensionIdentifier;
if (isIExtensionIdentifier(arg)) {
identifier = arg;
} else {
if (!this.canChangeEnablement(arg)) {
return TPromise.wrap(false);
return Promise.resolve(false);
}
identifier = arg.galleryIdentifier;
}
const workspace = newState === EnablementState.WorkspaceDisabled || newState === EnablementState.WorkspaceEnabled;
if (workspace && !this.hasWorkspace) {
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
return Promise.reject(new Error(localize('noWorkspace', "No workspace.")));
}
const currentState = this._getEnablementState(identifier);
if (currentState === newState) {
return TPromise.as(false);
return Promise.resolve(false);
}
@@ -141,7 +141,7 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
}
this._onEnablementChanged.fire(identifier);
return TPromise.as(true);
return Promise.resolve(true);
}
isEnabled(extension: ILocalExtension): boolean {
@@ -198,17 +198,17 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
this._removeFromEnabledExtensions(identifier, StorageScope.WORKSPACE);
}
private _addToDisabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): TPromise<boolean> {
private _addToDisabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): Promise<boolean> {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return TPromise.wrap(false);
return Promise.resolve(false);
}
let disabledExtensions = this._getDisabledExtensions(scope);
if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
disabledExtensions.push(identifier);
this._setDisabledExtensions(disabledExtensions, scope, identifier);
return TPromise.wrap(true);
return Promise.resolve(true);
}
return TPromise.wrap(false);
return Promise.resolve(false);
}
private _removeFromDisabledExtensions(identifier: IExtensionIdentifier, scope: StorageScope): boolean {
@@ -288,18 +288,32 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
}
}
private _onDidInstallExtension(event: DidInstallExtensionEvent): void {
if (event.local && event.operation === InstallOperation.Install) {
const wasDisabled = !this.isEnabled(event.local);
this._reset(event.local.galleryIdentifier);
if (wasDisabled) {
this._onEnablementChanged.fire(event.local.galleryIdentifier);
}
}
}
private _onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
if (!error) {
const id = getIdFromLocalExtensionId(identifier.id);
if (id) {
const extension = { id, uuid: identifier.uuid };
this._removeFromDisabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromEnabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromDisabledExtensions(extension, StorageScope.GLOBAL);
this._reset(extension);
}
}
}
private _reset(extension: IExtensionIdentifier) {
this._removeFromDisabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromEnabledExtensions(extension, StorageScope.WORKSPACE);
this._removeFromDisabledExtensions(extension, StorageScope.GLOBAL);
}
dispose(): void {
this.disposables = dispose(this.disposables);
}

View File

@@ -3,16 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
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';
import URI from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { CancellationToken } from 'vs/base/common/cancellation';
export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$';
export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
@@ -112,6 +110,8 @@ export interface IExtensionContributions {
localizations?: ILocalization[];
}
export type ExtensionKind = 'ui' | 'workspace';
export interface IExtensionManifest {
name: string;
publisher: string;
@@ -123,9 +123,11 @@ export interface IExtensionManifest {
main?: string;
icon?: string;
categories?: string[];
keywords?: string[];
activationEvents?: string[];
extensionDependencies?: string[];
extensionPack?: string[];
extensionKind?: ExtensionKind;
contributes?: IExtensionContributions;
repository?: {
url: string;
@@ -139,6 +141,7 @@ export interface IGalleryExtensionProperties {
dependencies?: string[];
extensionPack?: string[];
engine?: string;
localizedLanguages?: string[];
}
export interface IGalleryExtensionAsset {
@@ -177,6 +180,11 @@ export interface IExtensionIdentifier {
uuid?: string;
}
export interface IGalleryExtensionVersion {
version: string;
date: string;
}
export interface IGalleryExtension {
name: string;
identifier: IExtensionIdentifier;
@@ -202,7 +210,7 @@ export interface IGalleryMetadata {
publisherDisplayName: string;
}
export enum LocalExtensionType {
export const enum LocalExtensionType {
System,
User
}
@@ -221,7 +229,7 @@ export interface ILocalExtension {
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
export const IExtensionGalleryService = createDecorator<IExtensionGalleryService>('extensionGalleryService');
export enum SortBy {
export const enum SortBy {
NoneOrRelevance = 0,
LastUpdatedDate = 1,
Title = 2,
@@ -232,7 +240,7 @@ export enum SortBy {
WeightedRating = 12
}
export enum SortOrder {
export const enum SortOrder {
Default = 0,
Ascending = 1,
Descending = 2
@@ -248,7 +256,7 @@ export interface IQueryOptions {
source?: string;
}
export enum StatisticType {
export const enum StatisticType {
Uninstall = 'uninstall'
}
@@ -257,8 +265,9 @@ export interface IReportedExtension {
malicious: boolean;
}
export enum InstallOperation {
Install = 1,
export const enum InstallOperation {
None = 0,
Install,
Update
}
@@ -269,16 +278,18 @@ export interface ITranslation {
export interface IExtensionGalleryService {
_serviceBrand: any;
isEnabled(): boolean;
query(options?: IQueryOptions): TPromise<IPager<IGalleryExtension>>;
download(extension: IGalleryExtension, operation: InstallOperation): TPromise<string>;
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise<void>;
getReadme(extension: IGalleryExtension): TPromise<string>;
getManifest(extension: IGalleryExtension): TPromise<IExtensionManifest>;
getChangelog(extension: IGalleryExtension): TPromise<string>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): TPromise<ITranslation>;
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension>;
loadAllDependencies(dependencies: IExtensionIdentifier[]): TPromise<IGalleryExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
query(options?: IQueryOptions): Promise<IPager<IGalleryExtension>>;
download(extension: IGalleryExtension, operation: InstallOperation): Promise<string>;
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest>;
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation>;
loadCompatibleVersion(extension: IGalleryExtension, fromVersion?: string): Promise<IGalleryExtension>;
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]>;
loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]>;
getExtensionsReport(): Promise<IReportedExtension[]>;
getExtension(id: IExtensionIdentifier, version?: string): Promise<IGalleryExtension>;
}
export interface InstallExtensionEvent {
@@ -301,6 +312,9 @@ export interface DidUninstallExtensionEvent {
error?: string;
}
export const INSTALL_ERROR_MALICIOUS = 'malicious';
export const INSTALL_ERROR_INCOMPATIBLE = 'incompatible';
export interface IExtensionManagementService {
_serviceBrand: any;
@@ -309,31 +323,34 @@ export interface IExtensionManagementService {
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
install(zipPath: string): TPromise<void>;
installFromGallery(extension: IGalleryExtension): TPromise<void>;
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
reinstallFromGallery(extension: ILocalExtension): TPromise<void>;
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]>;
getExtensionsReport(): TPromise<IReportedExtension[]>;
zip(extension: ILocalExtension): Promise<URI>;
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier>;
install(vsix: URI): Promise<IExtensionIdentifier>;
installFromGallery(extension: IGalleryExtension): Promise<void>;
uninstall(extension: ILocalExtension, force?: boolean): Promise<void>;
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
getInstalled(type?: LocalExtensionType): Promise<ILocalExtension[]>;
getExtensionsReport(): Promise<IReportedExtension[]>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension>;
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>;
}
export const IExtensionManagementServerService = createDecorator<IExtensionManagementServerService>('extensionManagementServerService');
export interface IExtensionManagementServer {
extensionManagementService: IExtensionManagementService;
location: URI;
authority: string;
label: string;
}
export interface IExtensionManagementServerService {
_serviceBrand: any;
readonly extensionManagementServers: IExtensionManagementServer[];
getDefaultExtensionManagementServer(): IExtensionManagementServer;
getExtensionManagementServer(location: URI): IExtensionManagementServer;
readonly localExtensionManagementServer: IExtensionManagementServer | null;
readonly remoteExtensionManagementServer: IExtensionManagementServer | null;
getExtensionManagementServer(location: URI): IExtensionManagementServer | null;
}
export enum EnablementState {
export const enum EnablementState {
Disabled,
WorkspaceDisabled,
Enabled,
@@ -383,7 +400,7 @@ export interface IExtensionEnablementService {
*
* Throws error if enablement is requested for workspace and there is no workspace
*/
setEnablement(extension: ILocalExtension, state: EnablementState): TPromise<boolean>;
setEnablement(extension: ILocalExtension, state: EnablementState): Promise<boolean>;
}
export interface IExtensionsConfigContent {
@@ -413,17 +430,15 @@ export interface IExtensionTipsService {
_serviceBrand: any;
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; };
getFileBasedRecommendations(): IExtensionRecommendation[];
getOtherRecommendations(): TPromise<IExtensionRecommendation[]>;
getWorkspaceRecommendations(): TPromise<IExtensionRecommendation[]>;
getOtherRecommendations(): Promise<IExtensionRecommendation[]>;
getWorkspaceRecommendations(): Promise<IExtensionRecommendation[]>;
getKeymapRecommendations(): IExtensionRecommendation[];
getAllRecommendations(): TPromise<IExtensionRecommendation[]>;
getKeywordsForExtension(extension: string): string[];
toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void;
getAllIgnoredRecommendations(): { global: string[], workspace: string[] };
onRecommendationChange: Event<RecommendationChangeNotification>;
}
export enum ExtensionRecommendationReason {
export const enum ExtensionRecommendationReason {
Workspace,
File,
Executable,

View File

@@ -1,122 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from './extensionManagement';
import { Event, buffer, mapEvent } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
export interface IExtensionManagementChannel extends IChannel {
listen(event: 'onInstallExtension'): Event<InstallExtensionEvent>;
listen(event: 'onDidInstallExtension'): Event<DidInstallExtensionEvent>;
listen(event: 'onUninstallExtension'): Event<IExtensionIdentifier>;
listen(event: 'onDidUninstallExtension'): Event<DidUninstallExtensionEvent>;
call(command: 'install', args: [string]): TPromise<void>;
call(command: 'installFromGallery', args: [IGalleryExtension]): TPromise<void>;
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
call(command: 'reinstallFromGallery', args: [ILocalExtension]): TPromise<void>;
call(command: 'getInstalled', args: [LocalExtensionType]): TPromise<ILocalExtension[]>;
call(command: 'getExtensionsReport'): TPromise<IReportedExtension[]>;
call(command: 'updateMetadata', args: [ILocalExtension, IGalleryMetadata]): TPromise<ILocalExtension>;
}
export class ExtensionManagementChannel implements IExtensionManagementChannel {
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
constructor(private service: IExtensionManagementService) {
this.onInstallExtension = buffer(service.onInstallExtension, true);
this.onDidInstallExtension = buffer(service.onDidInstallExtension, true);
this.onUninstallExtension = buffer(service.onUninstallExtension, true);
this.onDidUninstallExtension = buffer(service.onDidUninstallExtension, true);
}
listen(event: string): Event<any> {
switch (event) {
case 'onInstallExtension': return this.onInstallExtension;
case 'onDidInstallExtension': return this.onDidInstallExtension;
case 'onUninstallExtension': return this.onUninstallExtension;
case 'onDidUninstallExtension': return this.onDidUninstallExtension;
}
throw new Error('Invalid listen');
}
call(command: string, args?: any): TPromise<any> {
switch (command) {
case 'install': return this.service.install(args[0]);
case 'installFromGallery': return this.service.installFromGallery(args[0]);
case 'uninstall': return this.service.uninstall(this._transform(args[0]), args[1]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(this._transform(args[0]));
case 'getInstalled': return this.service.getInstalled(args[0]);
case 'updateMetadata': return this.service.updateMetadata(this._transform(args[0]), args[1]);
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
throw new Error('Invalid call');
}
private _transform(extension: ILocalExtension): ILocalExtension {
return extension ? { ...extension, ...{ location: URI.revive(extension.location) } } : extension;
}
}
export class ExtensionManagementChannelClient implements IExtensionManagementService {
_serviceBrand: any;
constructor(private channel: IExtensionManagementChannel, private uriTransformer: IURITransformer) { }
get onInstallExtension(): Event<InstallExtensionEvent> { return this.channel.listen('onInstallExtension'); }
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return mapEvent(this.channel.listen('onDidInstallExtension'), i => ({ ...i, local: this._transformIncoming(i.local) })); }
get onUninstallExtension(): Event<IExtensionIdentifier> { return this.channel.listen('onUninstallExtension'); }
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
install(zipPath: string): TPromise<void> {
return this.channel.call('install', [zipPath]);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
return this.channel.call('installFromGallery', [extension]);
}
uninstall(extension: ILocalExtension, force = false): TPromise<void> {
return this.channel.call('uninstall', [this._transformOutgoing(extension), force]);
}
reinstallFromGallery(extension: ILocalExtension): TPromise<void> {
return this.channel.call('reinstallFromGallery', [this._transformOutgoing(extension)]);
}
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
return this.channel.call('getInstalled', [type])
.then(extensions => extensions.map(extension => this._transformIncoming(extension)));
}
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
return this.channel.call('updateMetadata', [this._transformOutgoing(local), metadata])
.then(extension => this._transformIncoming(extension));
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
return this.channel.call('getExtensionsReport');
}
private _transformIncoming(extension: ILocalExtension): ILocalExtension {
return extension ? { ...extension, ...{ location: URI.revive(this.uriTransformer.transformIncoming(extension.location)) } } : extension;
}
private _transformOutgoing(extension: ILocalExtension): ILocalExtension {
return extension ? { ...extension, ...{ location: this.uriTransformer.transformOutgoing(extension.location) } } : extension;
}
}

View File

@@ -3,9 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { compareIgnoreCase } from 'vs/base/common/strings';
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
if (a.uuid && b.uuid) {
@@ -14,7 +13,11 @@ export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifi
if (a.id === b.id) {
return true;
}
return adoptToGalleryExtensionId(a.id) === adoptToGalleryExtensionId(b.id);
return compareIgnoreCase(a.id, b.id) === 0;
}
export function adoptToGalleryExtensionId(id: string): string {
return id.toLocaleLowerCase();
}
export function getGalleryExtensionId(publisher: string, name: string): string {
@@ -35,10 +38,6 @@ export function getIdFromLocalExtensionId(localExtensionId: string): string {
return adoptToGalleryExtensionId(localExtensionId);
}
export function adoptToGalleryExtensionId(id: string): string {
return id.replace(EXTENSION_IDENTIFIER_REGEX, (match, publisher: string, name: string) => getGalleryExtensionId(publisher, name));
}
export function getLocalExtensionId(id: string, version: string): string {
return `${id}-${version}`;
}
@@ -99,12 +98,11 @@ export function getGalleryExtensionTelemetryData(extension: IGalleryExtension):
publisherId: extension.publisherId,
publisherName: extension.publisher,
publisherDisplayName: extension.publisherDisplayName,
dependencies: extension.properties.dependencies.length > 0,
dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0),
...extension.telemetryData
};
}
export const BetterMergeDisabledNowKey = 'extensions/bettermergedisablednow';
export const BetterMergeId = 'pprice.better-merge';
export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set<string> {

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { cloneAndChange } from 'vs/base/common/objects';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';

View File

@@ -1,67 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
IExtensionManagementService, ILocalExtension, IGalleryExtension, LocalExtensionType, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementServerService, IExtensionManagementServer
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { flatten } from 'vs/base/common/arrays';
export class MulitExtensionManagementService implements IExtensionManagementService {
_serviceBrand: any;
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
private readonly servers: IExtensionManagementServer[];
constructor(
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService
) {
this.servers = this.extensionManagementServerService.extensionManagementServers;
this.onInstallExtension = this.servers.reduce((emitter: EventMultiplexer<InstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onInstallExtension); return emitter; }, new EventMultiplexer<InstallExtensionEvent>()).event;
this.onDidInstallExtension = this.servers.reduce((emitter: EventMultiplexer<DidInstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidInstallExtension); return emitter; }, new EventMultiplexer<DidInstallExtensionEvent>()).event;
this.onUninstallExtension = this.servers.reduce((emitter: EventMultiplexer<IExtensionIdentifier>, server) => { emitter.add(server.extensionManagementService.onUninstallExtension); return emitter; }, new EventMultiplexer<IExtensionIdentifier>()).event;
this.onDidUninstallExtension = this.servers.reduce((emitter: EventMultiplexer<DidUninstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidUninstallExtension); return emitter; }, new EventMultiplexer<DidUninstallExtensionEvent>()).event;
}
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]> {
return TPromise.join(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type)))
.then(result => flatten(result));
}
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void> {
return this.getServer(extension).extensionManagementService.uninstall(extension, force);
}
reinstallFromGallery(extension: ILocalExtension): TPromise<void> {
return this.getServer(extension).extensionManagementService.reinstallFromGallery(extension);
}
updateMetadata(extension: ILocalExtension, metadata: IGalleryMetadata): TPromise<ILocalExtension> {
return this.getServer(extension).extensionManagementService.updateMetadata(extension, metadata);
}
install(zipPath: string): TPromise<void> {
return this.servers[0].extensionManagementService.install(zipPath);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
return TPromise.join(this.servers.map(server => server.extensionManagementService.installFromGallery(extension))).then(() => null);
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
return this.servers[0].extensionManagementService.getExtensionsReport();
}
private getServer(extension: ILocalExtension): IExtensionManagementServer {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
}

View File

@@ -3,13 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { tmpdir } from 'os';
import * as path from 'path';
import { TPromise } from 'vs/base/common/winjs.base';
import { distinct } from 'vs/base/common/arrays';
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors';
import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionManifest, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId, getGalleryExtensionTelemetryData, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService } from 'vs/platform/request/node/request';
@@ -27,6 +25,9 @@ import { values } from 'vs/base/common/map';
// {{SQL CARBON EDIT}}
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/parts/extensions/common/extensions';
// {{SQL CARBON EDIT}} - End
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
interface IRawGalleryExtensionFile {
assetType: string;
@@ -121,7 +122,8 @@ const AssetType = {
const PropertyType = {
Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',
ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',
Engine: 'Microsoft.VisualStudio.Code.Engine'
Engine: 'Microsoft.VisualStudio.Code.Engine',
LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages'
};
interface ICriterium {
@@ -296,18 +298,23 @@ function getEngine(version: IRawGalleryExtensionVersion): string {
return (values.length > 0 && values[0].value) || '';
}
function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];
const value = (values.length > 0 && values[0].value) || '';
return value ? value.split(',') : [];
}
function getIsPreview(flags: string): boolean {
return flags.indexOf('preview') !== -1;
}
function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUrl: string, index: number, query: Query, querySource?: string): IGalleryExtension {
const [version] = galleryExtension.versions;
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, index: number, query: Query, querySource?: string): IGalleryExtension {
const assets = {
manifest: getVersionAsset(version, AssetType.Manifest),
readme: getVersionAsset(version, AssetType.Details),
changelog: getVersionAsset(version, AssetType.Changelog),
download: getVersionAsset(version, AssetType.VSIX),
// {{SQL CARBON EDIT}}
// {{SQL CARBON EDIT}} - Add downloadPage
downloadPage: getVersionAsset(version, AssetType.DownloadPage),
icon: getVersionAsset(version, AssetType.Icon),
license: getVersionAsset(version, AssetType.License),
@@ -335,7 +342,8 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
properties: {
dependencies: getExtensions(version, PropertyType.Dependency),
extensionPack: getExtensions(version, PropertyType.ExtensionPack),
engine: getEngine(version)
engine: getEngine(version),
localizedLanguages: getLocalizedLanguages(version)
},
/* __GDPR__FRAGMENT__
"GalleryExtensionTelemetryData2" : {
@@ -365,10 +373,11 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
private extensionsGalleryUrl: string;
private extensionsControlUrl: string;
private readonly commonHeadersPromise: TPromise<{ [key: string]: string; }>;
private readonly commonHeadersPromise: Promise<{ [key: string]: string; }>;
constructor(
@IRequestService private requestService: IRequestService,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ITelemetryService private telemetryService: ITelemetryService,
// {{SQL CARBON EDIT}}
@@ -389,9 +398,34 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return !!this.extensionsGalleryUrl;
}
query(options: IQueryOptions = {}): TPromise<IPager<IGalleryExtension>> {
getExtension({ id, uuid }: IExtensionIdentifier, version?: string): Promise<IGalleryExtension> {
let query = new Query()
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished));
if (uuid) {
query = query.withFilter(FilterType.ExtensionId, uuid);
} else {
query = query.withFilter(FilterType.ExtensionName, id);
}
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => {
if (galleryExtensions.length) {
const galleryExtension = galleryExtensions[0];
const versionAsset = version ? galleryExtension.versions.filter(v => v.version === version)[0] : galleryExtension.versions[0];
if (versionAsset) {
return toExtension(galleryExtension, versionAsset, 0, query);
}
}
return null;
});
}
query(options: IQueryOptions = {}): Promise<IPager<IGalleryExtension>> {
if (!this.isEnabled()) {
return TPromise.wrapError<IPager<IGalleryExtension>>(new Error('No extension gallery service configured.'));
return Promise.reject(new Error('No extension gallery service configured.'));
}
const type = options.names ? 'ids' : (options.text ? 'text' : 'all');
@@ -449,18 +483,21 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
query = query.withSortOrder(options.sortOrder);
}
return this.queryGallery(query).then(({ galleryExtensions, total }) => {
const extensions = galleryExtensions.map((e, index) => toExtension(e, this.extensionsGalleryUrl, index, query, options.source));
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions, total }) => {
const extensions = galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, query, options.source));
// {{SQL CARBON EDIT}}
const pageSize = extensions.length;
const getPage = (pageIndex: number) => {
const getPage = (pageIndex: number, ct: CancellationToken) => {
if (ct.isCancellationRequested) {
return Promise.reject(canceled());
}
const nextPageQuery = query.withPage(pageIndex + 1);
return this.queryGallery(nextPageQuery)
.then(({ galleryExtensions }) => galleryExtensions.map((e, index) => toExtension(e, this.extensionsGalleryUrl, index, nextPageQuery, options.source)));
return this.queryGallery(nextPageQuery, ct)
.then(({ galleryExtensions }) => galleryExtensions.map((e, index) => toExtension(e, e.versions[0], index, nextPageQuery, options.source)));
};
return { firstPage: extensions, total, pageSize, getPage };
return { firstPage: extensions, total, pageSize, getPage } as IPager<IGalleryExtension>;
});
}
@@ -544,7 +581,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return a[fieldName] < b[fieldName] ? -1 : 1;
}
private queryGallery(query: Query): TPromise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
private queryGallery(query: Query, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
return this.commonHeadersPromise.then(commonHeaders => {
const data = JSON.stringify(query.raw);
const headers = assign({}, commonHeaders, {
@@ -560,7 +597,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
url: this.api('/extensionquery'),
data,
headers
}).then(context => {
}, token).then(context => {
// {{SQL CARBON EDIT}}
let extensionPolicy: string = this.configurationService.getValue<string>(ExtensionsPolicyKey);
@@ -583,9 +620,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise<void> {
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {
if (!this.isEnabled()) {
return TPromise.as(null);
return Promise.resolve(null);
}
return this.commonHeadersPromise.then(commonHeaders => {
@@ -595,79 +632,101 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
type: 'POST',
url: this.api(`/publishers/${publisher}/extensions/${name}/${version}/stats?statType=${type}`),
headers
}).then(null, () => null);
}, CancellationToken.None).then(null, () => null);
});
}
download(extension: IGalleryExtension, operation: InstallOperation): TPromise<string> {
return this.loadCompatibleVersion(extension)
.then(extension => {
if (!extension) {
return TPromise.wrapError(new Error(localize('notCompatibleDownload', "Unable to download because the extension compatible with current version '{0}' of VS Code is not found.", pkg.version)));
}
const zipPath = path.join(tmpdir(), generateUuid());
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration }));
download(extension: IGalleryExtension, operation: InstallOperation): Promise<string> {
this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);
const zipPath = path.join(tmpdir(), generateUuid());
const data = getGalleryExtensionTelemetryData(extension);
const startTime = new Date().getTime();
/* __GDPR__
"galleryService:downloadVSIX" : {
"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
const log = (duration: number) => this.telemetryService.publicLog('galleryService:downloadVSIX', assign(data, { duration }));
// {{SQL Carbon Edit}} - Don't append install or update on to the URL
// const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
const operationParam = undefined;
const downloadAsset = operationParam ? {
uri: `${extension.assets.download.uri}&${operationParam}=true`,
fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true`
} : extension.assets.download;
// {{SQL Carbon Edit}} - Don't append install or update on to the URL
// const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';
const operationParam = undefined;
const downloadAsset = operationParam ? {
uri: `${extension.assets.download.uri}&${operationParam}=true`,
fallbackUri: `${extension.assets.download.fallbackUri}?${operationParam}=true`
} : extension.assets.download;
return this.getAsset(downloadAsset)
.then(context => download(zipPath, context))
.then(() => log(new Date().getTime() - startTime))
.then(() => zipPath);
});
return this.getAsset(downloadAsset)
.then(context => download(zipPath, context))
.then(() => log(new Date().getTime() - startTime))
.then(() => zipPath);
}
getReadme(extension: IGalleryExtension): TPromise<string> {
return this.getAsset(extension.assets.readme)
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
return this.getAsset(extension.assets.readme, {}, token)
.then(asText);
}
getManifest(extension: IGalleryExtension): TPromise<IExtensionManifest> {
return this.getAsset(extension.assets.manifest)
getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest> {
return this.getAsset(extension.assets.manifest, {}, token)
.then(asText)
.then(JSON.parse);
}
getCoreTranslation(extension: IGalleryExtension, languageId: string): TPromise<ITranslation> {
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation> {
const asset = extension.assets.coreTranslations[languageId.toUpperCase()];
if (asset) {
return this.getAsset(asset)
.then(asText)
.then(JSON.parse);
}
return TPromise.as(null);
return Promise.resolve(null);
}
getChangelog(extension: IGalleryExtension): TPromise<string> {
return this.getAsset(extension.assets.changelog)
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {
return this.getAsset(extension.assets.changelog, {}, token)
.then(asText);
}
loadAllDependencies(extensions: IExtensionIdentifier[]): TPromise<IGalleryExtension[]> {
return this.getDependenciesReccursively(extensions.map(e => e.id), []);
loadAllDependencies(extensions: IExtensionIdentifier[], token: CancellationToken): Promise<IGalleryExtension[]> {
return this.getDependenciesReccursively(extensions.map(e => e.id), [], token);
}
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension> {
if (extension.properties.engine && isEngineValid(extension.properties.engine)) {
return TPromise.wrap(extension);
getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise<IGalleryExtensionVersion[]> {
let query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished));
if (extension.identifier.uuid) {
query = query.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
} else {
query = query.withFilter(FilterType.ExtensionName, extension.identifier.id);
}
return this.queryGallery(query, CancellationToken.None).then(({ galleryExtensions }) => {
if (galleryExtensions.length) {
if (compatible) {
return Promise.all(galleryExtensions[0].versions.map(v => this.getEngine(v).then(engine => isEngineValid(engine) ? v : null)))
.then(versions => versions
.filter(v => !!v)
.map(v => ({ version: v.version, date: v.lastUpdated })));
} else {
return galleryExtensions[0].versions.map(v => ({ version: v.version, date: v.lastUpdated }));
}
}
return [];
});
}
loadCompatibleVersion(extension: IGalleryExtension, fromVersion: string = extension.version): Promise<IGalleryExtension> {
if (extension.version === fromVersion && extension.properties.engine && isEngineValid(extension.properties.engine)) {
return Promise.resolve(extension);
}
const query = new Query()
.withFlags(Flags.IncludeVersions, Flags.IncludeFiles, Flags.IncludeVersionProperties)
.withPage(1, 1)
@@ -676,7 +735,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withAssetTypes(AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
return this.queryGallery(query)
return this.queryGallery(query, CancellationToken.None)
.then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
@@ -684,25 +743,43 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return null;
}
return this.getLastValidExtensionVersion(rawExtension, rawExtension.versions)
const versions: IRawGalleryExtensionVersion[] = this.getVersionsFrom(rawExtension.versions, fromVersion);
if (!versions.length) {
return null;
}
return this.getLastValidExtensionVersion(rawExtension, versions)
.then(rawVersion => {
if (rawVersion) {
extension.properties.dependencies = getExtensions(rawVersion, PropertyType.Dependency);
extension.properties.engine = getEngine(rawVersion);
// {{SQL CARBON EDIT}}
extension.assets.download = getVersionAsset(rawVersion, AssetType.VSIX);
extension.assets.downloadPage = getVersionAsset(rawVersion, AssetType.DownloadPage);
extension.version = rawVersion.version;
return extension;
return toExtension(rawExtension, rawVersion, 0, query);
}
return null;
});
});
}
private loadDependencies(extensionNames: string[]): TPromise<IGalleryExtension[]> {
private getVersionsFrom(versions: IRawGalleryExtensionVersion[], version: string): IRawGalleryExtensionVersion[] {
if (versions[0].version === version) {
return versions;
}
const result: IRawGalleryExtensionVersion[] = [];
let currentVersion: IRawGalleryExtensionVersion = null;
for (const v of versions) {
if (!currentVersion) {
if (v.version === version) {
currentVersion = v;
}
}
if (currentVersion) {
result.push(v);
}
}
return result;
}
private loadDependencies(extensionNames: string[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!extensionNames || extensionNames.length === 0) {
return TPromise.as([]);
return Promise.resolve([]);
}
let query = new Query()
@@ -713,14 +790,14 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withAssetTypes(AssetType.Icon, AssetType.License, AssetType.Details, AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionName, ...extensionNames);
return this.queryGallery(query).then(result => {
const dependencies = [];
const ids = [];
return this.queryGallery(query, token).then(result => {
const dependencies: IGalleryExtension[] = [];
const ids: string[] = [];
for (let index = 0; index < result.galleryExtensions.length; index++) {
const rawExtension = result.galleryExtensions[index];
if (ids.indexOf(rawExtension.extensionId) === -1) {
dependencies.push(toExtension(rawExtension, this.extensionsGalleryUrl, index, query, 'dependencies'));
dependencies.push(toExtension(rawExtension, rawExtension.versions[0], index, query, 'dependencies'));
ids.push(rawExtension.extensionId);
}
}
@@ -728,16 +805,16 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
});
}
private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[]): TPromise<IGalleryExtension[]> {
private getDependenciesReccursively(toGet: string[], result: IGalleryExtension[], token: CancellationToken): Promise<IGalleryExtension[]> {
if (!toGet || !toGet.length) {
return TPromise.wrap(result);
return Promise.resolve(result);
}
toGet = result.length ? toGet.filter(e => !ExtensionGalleryService.hasExtensionByName(result, e)) : toGet;
if (!toGet.length) {
return TPromise.wrap(result);
return Promise.resolve(result);
}
return this.loadDependencies(toGet)
return this.loadDependencies(toGet, token)
.then(loadedDependencies => {
const dependenciesSet = new Set<string>();
for (const dep of loadedDependencies) {
@@ -748,11 +825,11 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
result = distinct(result.concat(loadedDependencies), d => d.identifier.uuid);
const dependencies: string[] = [];
dependenciesSet.forEach(d => !ExtensionGalleryService.hasExtensionByName(result, d) && dependencies.push(d));
return this.getDependenciesReccursively(dependencies, result);
return this.getDependenciesReccursively(dependencies, result, token);
});
}
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}): TPromise<IRequestContext> {
private getAsset(asset: IGalleryExtensionAsset, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {
return this.commonHeadersPromise.then(commonHeaders => {
const baseOptions = { type: 'GET' };
const headers = assign({}, commonHeaders, options.headers || {});
@@ -762,18 +839,18 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
const fallbackUrl = asset.fallbackUri;
const firstOptions = assign({}, options, { url });
return this.requestService.request(firstOptions)
return this.requestService.request(firstOptions, token)
.then(context => {
if (context.res.statusCode === 200) {
return TPromise.as(context);
return Promise.resolve(context);
}
return asText(context)
.then(message => TPromise.wrapError<IRequestContext>(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
.then(message => Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`)));
})
.then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
return Promise.reject(err);
}
const message = getErrorMessage(err);
@@ -794,9 +871,9 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
this.telemetryService.publicLog('galleryService:cdnFallback', { url, message });
const fallbackOptions = assign({}, options, { url: fallbackUrl });
return this.requestService.request(fallbackOptions).then(null, err => {
return this.requestService.request(fallbackOptions, token).then(null, err => {
if (isPromiseCanceledError(err)) {
return TPromise.wrapError<IRequestContext>(err);
return Promise.reject(err);
}
const message = getErrorMessage(err);
@@ -808,13 +885,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
*/
this.telemetryService.publicLog('galleryService:requestError', { url: fallbackUrl, cdn: false, message });
return TPromise.wrapError<IRequestContext>(err);
return Promise.reject(err);
});
});
});
}
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersion(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
const version = this.getLastValidExtensionVersionFromProperties(extension, versions);
if (version) {
return version;
@@ -822,39 +899,47 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return this.getLastValidExtensionVersionReccursively(extension, versions);
}
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
private getLastValidExtensionVersionFromProperties(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
for (const version of versions) {
const engine = getEngine(version);
if (!engine) {
return null;
}
if (isEngineValid(engine)) {
return TPromise.wrap(version);
return Promise.resolve(version);
}
}
return null;
}
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): TPromise<IRawGalleryExtensionVersion> {
if (!versions.length) {
return null;
private getEngine(version: IRawGalleryExtensionVersion): Promise<string> {
const engine = getEngine(version);
if (engine) {
return Promise.resolve(engine);
}
const version = versions[0];
const asset = getVersionAsset(version, AssetType.Manifest);
const headers = { 'Accept-Encoding': 'gzip' };
return this.getAsset(asset, { headers })
.then(context => asJson<IExtensionManifest>(context))
.then(manifest => {
const engine = manifest.engines.vscode;
.then(manifest => manifest.engines.vscode);
}
private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise<IRawGalleryExtensionVersion> {
if (!versions.length) {
return null;
}
const version = versions[0];
return this.getEngine(version)
.then(engine => {
if (!isEngineValid(engine)) {
return this.getLastValidExtensionVersionReccursively(extension, versions.slice(1));
}
version.properties = version.properties || [];
version.properties.push({ key: PropertyType.Engine, value: manifest.engines.vscode });
version.properties.push({ key: PropertyType.Engine, value: engine });
return version;
});
}
@@ -868,18 +953,18 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
return false;
}
getExtensionsReport(): TPromise<IReportedExtension[]> {
getExtensionsReport(): Promise<IReportedExtension[]> {
if (!this.isEnabled()) {
return TPromise.wrapError(new Error('No extension gallery service configured.'));
return Promise.reject(new Error('No extension gallery service configured.'));
}
if (!this.extensionsControlUrl) {
return TPromise.as([]);
return Promise.resolve([]);
}
return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }).then(context => {
return this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None).then(context => {
if (context.res.statusCode !== 200) {
return TPromise.wrapError(new Error('Could not get extensions report.'));
return Promise.reject(new Error('Could not get extensions report.'));
}
return asJson<IRawExtensionsReport>(context).then(result => {
@@ -891,13 +976,13 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
map.set(id, ext);
}
return TPromise.as(values(map));
return Promise.resolve(values(map));
});
});
}
}
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): TPromise<{ [key: string]: string; }> {
export function resolveMarketplaceHeaders(environmentService: IEnvironmentService): Promise<{ [key: string]: string; }> {
const marketplaceMachineIdFile = path.join(environmentService.userDataPath, 'machineid');
return readFile(marketplaceMachineIdFile, 'utf8').then(contents => {
@@ -905,9 +990,9 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic
return contents;
}
return TPromise.wrap(null); // invalid marketplace UUID
return Promise.resolve(null); // invalid marketplace UUID
}, error => {
return TPromise.wrap(null); // error reading ID file
return Promise.resolve(null); // error reading ID file
}).then(uuid => {
if (!uuid) {
uuid = generateUuid();
@@ -925,4 +1010,4 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic
'X-Market-User-Id': uuid
};
});
}
}

View File

@@ -2,11 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { TPromise } from 'vs/base/common/winjs.base';
import { ILogService } from 'vs/platform/log/common/log';
import { fork, ChildProcess } from 'child_process';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -14,97 +12,106 @@ import { posix } from 'path';
import { Limiter } from 'vs/base/common/async';
import { fromNodeEventEmitter, anyEvent, mapEvent, debounceEvent } from 'vs/base/common/event';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { rimraf } from 'vs/base/node/pfs';
export class ExtensionsLifecycle extends Disposable {
private processesLimiter: Limiter<void> = new Limiter(5); // Run max 5 processes in parallel
constructor(
@ILogService private logService: ILogService
private environmentService: IEnvironmentService,
private logService: ILogService
) {
super();
}
uninstall(extension: ILocalExtension): TPromise<void> {
const uninstallScript = this.parseUninstallScript(extension);
if (uninstallScript) {
this.logService.info(extension.identifier.id, 'Running Uninstall hook');
return this.processesLimiter.queue(() =>
this.runUninstallHook(uninstallScript.uninstallHook, uninstallScript.args, extension)
.then(() => this.logService.info(extension.identifier.id, 'Finished running uninstall hook'), err => this.logService.error(extension.identifier.id, `Failed to run uninstall hook: ${err}`)));
async postUninstall(extension: ILocalExtension): Promise<void> {
const script = this.parseScript(extension, 'uninstall');
if (script) {
this.logService.info(extension.identifier.id, `Running post uninstall script`);
await this.processesLimiter.queue(() =>
this.runLifecycleHook(script.script, 'uninstall', script.args, true, extension)
.then(() => this.logService.info(extension.identifier.id, `Finished running post uninstall script`), err => this.logService.error(extension.identifier.id, `Failed to run post uninstall script: ${err}`)));
}
return TPromise.as(null);
return rimraf(this.getExtensionStoragePath(extension)).then(null, e => this.logService.error('Error while removing extension storage path', e));
}
private parseUninstallScript(extension: ILocalExtension): { uninstallHook: string, args: string[] } {
if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts']['vscode:uninstall'] === 'string') {
const uninstallScript = (<string>extension.manifest['scripts']['vscode:uninstall']).split(' ');
if (uninstallScript.length < 2 || uninstallScript[0] !== 'node' || !uninstallScript[1]) {
this.logService.warn(extension.identifier.id, 'Uninstall script should be a node script');
private parseScript(extension: ILocalExtension, type: string): { script: string, args: string[] } | null {
const scriptKey = `vscode:${type}`;
if (extension.location.scheme === Schemas.file && extension.manifest && extension.manifest['scripts'] && typeof extension.manifest['scripts'][scriptKey] === 'string') {
const script = (<string>extension.manifest['scripts'][scriptKey]).split(' ');
if (script.length < 2 || script[0] !== 'node' || !script[1]) {
this.logService.warn(extension.identifier.id, `${scriptKey} should be a node script`);
return null;
}
return { uninstallHook: posix.join(extension.location.fsPath, uninstallScript[1]), args: uninstallScript.slice(2) || [] };
return { script: posix.join(extension.location.fsPath, script[1]), args: script.slice(2) || [] };
}
return null;
}
private runUninstallHook(lifecycleHook: string, args: string[], extension: ILocalExtension): TPromise<void> {
return new TPromise((c, e) => {
private runLifecycleHook(lifecycleHook: string, lifecycleType: string, args: string[], timeout: boolean, extension: ILocalExtension): Thenable<void> {
return new Promise<void>((c, e) => {
const extensionLifecycleProcess = this.start(lifecycleHook, args, extension);
const extensionLifecycleProcess = this.start(lifecycleHook, lifecycleType, args, extension);
let timeoutHandler;
const onexit = (error?: string) => {
clearTimeout(timeoutHandler);
timeoutHandler = null;
if (timeoutHandler) {
clearTimeout(timeoutHandler);
timeoutHandler = null;
}
if (error) {
e(error);
} else {
c(null);
c(void 0);
}
};
// on error
extensionLifecycleProcess.on('error', (err) => {
if (timeoutHandler) {
onexit(toErrorMessage(err) || 'Unknown');
}
onexit(toErrorMessage(err) || 'Unknown');
});
// on exit
extensionLifecycleProcess.on('exit', (code: number, signal: string) => {
if (timeoutHandler) {
onexit(code ? `Process exited with code ${code}` : void 0);
}
onexit(code ? `post-${lifecycleType} process exited with code ${code}` : void 0);
});
// timeout: kill process after waiting for 5s
timeoutHandler = setTimeout(() => {
timeoutHandler = null;
extensionLifecycleProcess.kill();
e('timed out');
}, 5000);
if (timeout) {
// timeout: kill process after waiting for 5s
timeoutHandler = setTimeout(() => {
timeoutHandler = null;
extensionLifecycleProcess.kill();
e('timed out');
}, 5000);
}
});
}
private start(uninstallHook: string, args: string[], extension: ILocalExtension): ChildProcess {
private start(uninstallHook: string, lifecycleType: string, args: string[], extension: ILocalExtension): ChildProcess {
const opts = {
silent: true,
execArgv: <string[]>undefined
execArgv: undefined
};
const extensionUninstallProcess = fork(uninstallHook, ['--type=extensionUninstall', ...args], opts);
const extensionUninstallProcess = fork(uninstallHook, [`--type=extension-post-${lifecycleType}`, ...args], opts);
// Catch all output coming from the process
type Output = { data: string, format: string[] };
extensionUninstallProcess.stdout.setEncoding('utf8');
extensionUninstallProcess.stderr.setEncoding('utf8');
const onStdout = fromNodeEventEmitter<string>(extensionUninstallProcess.stdout, 'data');
const onStderr = fromNodeEventEmitter<string>(extensionUninstallProcess.stderr, 'data');
// Log output
onStdout(data => this.logService.info(extension.identifier.id, `post-${lifecycleType}`, data));
onStderr(data => this.logService.error(extension.identifier.id, `post-${lifecycleType}`, data));
const onOutput = anyEvent(
mapEvent(onStdout, o => ({ data: `%c${o}`, format: [''] })),
mapEvent(onStderr, o => ({ data: `%c${o}`, format: ['color: red'] }))
);
// Debounce all output, so we can render it in the Chrome console as a group
const onDebouncedOutput = debounceEvent<Output>(onOutput, (r, o) => {
return r
@@ -112,7 +119,7 @@ export class ExtensionsLifecycle extends Disposable {
: { data: o.data, format: o.format };
}, 100);
// Print out extension host output
// Print out output
onDebouncedOutput(data => {
console.group(extension.identifier.id);
console.log(data.data, ...data.format);
@@ -121,4 +128,8 @@ export class ExtensionsLifecycle extends Disposable {
return extensionUninstallProcess;
}
private getExtensionStoragePath(extension: ILocalExtension): string {
return posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLocaleLowerCase());
}
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement';
import { Event, buffer, mapEvent } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { IURITransformer } from 'vs/base/common/uriIpc';
export class ExtensionManagementChannel implements IServerChannel {
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
constructor(private service: IExtensionManagementService, private getUriTransformer: (requestContext: any) => IURITransformer) {
this.onInstallExtension = buffer(service.onInstallExtension, true);
this.onDidInstallExtension = buffer(service.onDidInstallExtension, true);
this.onUninstallExtension = buffer(service.onUninstallExtension, true);
this.onDidUninstallExtension = buffer(service.onDidUninstallExtension, true);
}
listen(context, event: string): Event<any> {
const uriTransformer = this.getUriTransformer(context);
switch (event) {
case 'onInstallExtension': return this.onInstallExtension;
case 'onDidInstallExtension': return mapEvent(this.onDidInstallExtension, i => ({ ...i, local: this._transformOutgoing(i.local, uriTransformer) }));
case 'onUninstallExtension': return this.onUninstallExtension;
case 'onDidUninstallExtension': return this.onDidUninstallExtension;
}
throw new Error('Invalid listen');
}
call(context, command: string, args?: any): Thenable<any> {
const uriTransformer = this.getUriTransformer(context);
switch (command) {
case 'zip': return this.service.zip(this._transformIncoming(args[0], uriTransformer)).then(uri => uriTransformer.transformOutgoing(uri));
case 'unzip': return this.service.unzip(URI.revive(uriTransformer.transformIncoming(args[0])), args[1]);
case 'install': return this.service.install(URI.revive(uriTransformer.transformIncoming(args[0])));
case 'installFromGallery': return this.service.installFromGallery(args[0]);
case 'uninstall': return this.service.uninstall(this._transformIncoming(args[0], uriTransformer), args[1]);
case 'reinstallFromGallery': return this.service.reinstallFromGallery(this._transformIncoming(args[0], uriTransformer));
case 'getInstalled': return this.service.getInstalled(args[0]).then(extensions => extensions.map(e => this._transformOutgoing(e, uriTransformer)));
case 'updateMetadata': return this.service.updateMetadata(this._transformIncoming(args[0], uriTransformer), args[1]).then(e => this._transformOutgoing(e, uriTransformer));
case 'getExtensionsReport': return this.service.getExtensionsReport();
}
throw new Error('Invalid call');
}
private _transformIncoming(extension: ILocalExtension, uriTransformer: IURITransformer): ILocalExtension {
return extension ? { ...extension, ...{ location: URI.revive(uriTransformer.transformIncoming(extension.location)) } } : extension;
}
private _transformOutgoing(extension: ILocalExtension, uriTransformer: IURITransformer): ILocalExtension;
private _transformOutgoing(extension: ILocalExtension | undefined, uriTransformer: IURITransformer): ILocalExtension | undefined;
private _transformOutgoing(extension: ILocalExtension | undefined, uriTransformer: IURITransformer): ILocalExtension | undefined {
return extension ? { ...extension, ...{ location: uriTransformer.transformOutgoing(extension.location) } } : extension;
}
}
export class ExtensionManagementChannelClient implements IExtensionManagementService {
_serviceBrand: any;
constructor(private channel: IChannel) { }
get onInstallExtension(): Event<InstallExtensionEvent> { return this.channel.listen('onInstallExtension'); }
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return mapEvent(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension'), i => ({ ...i, local: this._transformIncoming(i.local) })); }
get onUninstallExtension(): Event<IExtensionIdentifier> { return this.channel.listen('onUninstallExtension'); }
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
zip(extension: ILocalExtension): Promise<URI> {
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result)));
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
return Promise.resolve(this.channel.call('unzip', [zipLocation, type]));
}
install(vsix: URI): Promise<IExtensionIdentifier> {
return Promise.resolve(this.channel.call('install', [vsix]));
}
installFromGallery(extension: IGalleryExtension): Promise<void> {
return Promise.resolve(this.channel.call('installFromGallery', [extension]));
}
uninstall(extension: ILocalExtension, force = false): Promise<void> {
return Promise.resolve(this.channel.call('uninstall', [extension!, force]));
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
return Promise.resolve(this.channel.call('reinstallFromGallery', [extension]));
}
getInstalled(type: LocalExtensionType | null = null): Promise<ILocalExtension[]> {
return Promise.resolve(this.channel.call<ILocalExtension[]>('getInstalled', [type]))
.then(extensions => extensions.map(extension => this._transformIncoming(extension)));
}
updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
return Promise.resolve(this.channel.call<ILocalExtension>('updateMetadata', [local, metadata]))
.then(extension => this._transformIncoming(extension));
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
private _transformIncoming(extension: ILocalExtension): ILocalExtension;
private _transformIncoming(extension: ILocalExtension | undefined): ILocalExtension | undefined;
private _transformIncoming(extension: ILocalExtension | undefined): ILocalExtension | undefined {
return extension ? { ...extension, ...{ location: URI.revive(extension.location) } } : extension;
}
}

View File

@@ -3,12 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as semver from 'semver';
import { adoptToGalleryExtensionId, LOCAL_EXTENSION_ID_REGEX } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { buffer } from 'vs/platform/node/zip';
import { localize } from 'vs/nls';
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string } {
export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): { id: string, version: string | null } {
const matches = LOCAL_EXTENSION_ID_REGEX.exec(localExtensionId);
if (matches && matches[1] && matches[2]) {
const version = semver.valid(matches[2]);
@@ -20,4 +21,15 @@ export function getIdAndVersionFromLocalExtensionId(localExtensionId: string): {
id: adoptToGalleryExtensionId(localExtensionId),
version: null
};
}
export function getManifest(vsix: string): Promise<IExtensionManifest> {
return buffer(vsix, 'extension/package.json')
.then(buffer => {
try {
return JSON.parse(buffer.toString('utf8'));
} catch (err) {
throw new Error(localize('invalidManifest', "VSIX invalid: package.json is not a JSON file."));
}
});
}

View File

@@ -2,7 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable } from 'vs/base/common/lifecycle';
import { join } from 'path';
@@ -37,6 +36,6 @@ export class ExtensionsManifestCache extends Disposable {
}
invalidate(): void {
pfs.del(this.extensionsManifestCache).done(() => { }, () => { });
pfs.del(this.extensionsManifestCache).then(() => { }, () => { });
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
IExtensionManagementService, ILocalExtension, IGalleryExtension, LocalExtensionType, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata,
IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { flatten } from 'vs/base/common/arrays';
import { isUIExtension } from 'vs/platform/extensions/common/extensions';
import { URI } from 'vs/base/common/uri';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IRemoteAuthorityResolverService, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
export class MulitExtensionManagementService extends Disposable implements IExtensionManagementService {
_serviceBrand: any;
readonly onInstallExtension: Event<InstallExtensionEvent>;
readonly onDidInstallExtension: Event<DidInstallExtensionEvent>;
readonly onUninstallExtension: Event<IExtensionIdentifier>;
readonly onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
private readonly servers: IExtensionManagementServer[];
constructor(
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@IExtensionGalleryService private extensionGalleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@IRemoteAuthorityResolverService private remoteAuthorityResolverService: IRemoteAuthorityResolverService
) {
super();
this.servers = this.extensionManagementServerService.remoteExtensionManagementServer ? [this.extensionManagementServerService.localExtensionManagementServer, this.extensionManagementServerService.remoteExtensionManagementServer] : [this.extensionManagementServerService.localExtensionManagementServer];
this.onInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<InstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onInstallExtension); return emitter; }, new EventMultiplexer<InstallExtensionEvent>())).event;
this.onDidInstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<DidInstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidInstallExtension); return emitter; }, new EventMultiplexer<DidInstallExtensionEvent>())).event;
this.onUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<IExtensionIdentifier>, server) => { emitter.add(server.extensionManagementService.onUninstallExtension); return emitter; }, new EventMultiplexer<IExtensionIdentifier>())).event;
this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<DidUninstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidUninstallExtension); return emitter; }, new EventMultiplexer<DidUninstallExtensionEvent>())).event;
}
getInstalled(type?: LocalExtensionType): Promise<ILocalExtension[]> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type)))
.then(result => flatten(result));
}
uninstall(extension: ILocalExtension, force?: boolean): Promise<void> {
return this.getServer(extension).extensionManagementService.uninstall(extension, force);
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
return this.getServer(extension).extensionManagementService.reinstallFromGallery(extension);
}
updateMetadata(extension: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension> {
return this.getServer(extension).extensionManagementService.updateMetadata(extension, metadata);
}
zip(extension: ILocalExtension): Promise<URI> {
throw new Error('Not Supported');
}
unzip(zipLocation: URI, type: LocalExtensionType): Promise<IExtensionIdentifier> {
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation, type))).then(() => null);
}
install(vsix: URI): Promise<IExtensionIdentifier> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix);
}
return Promise.all([getManifest(vsix.fsPath), this.hasToSyncExtensions()])
.then(([manifest, syncExtensions]) => {
const servers = isUIExtension(manifest, this.configurationService) ? [this.extensionManagementServerService.localExtensionManagementServer] : syncExtensions ? this.servers : [this.extensionManagementServerService.remoteExtensionManagementServer];
return Promise.all(servers.map(server => server.extensionManagementService.install(vsix)))
.then(() => null);
});
}
installFromGallery(gallery: IGalleryExtension): Promise<void> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(gallery);
}
return Promise.all([this.extensionGalleryService.getManifest(gallery, CancellationToken.None), this.hasToSyncExtensions()])
.then(([manifest, syncExtensions]) => {
const servers = isUIExtension(manifest, this.configurationService) ? [this.extensionManagementServerService.localExtensionManagementServer] : syncExtensions ? this.servers : [this.extensionManagementServerService.remoteExtensionManagementServer];
return Promise.all(servers.map(server => server.extensionManagementService.installFromGallery(gallery)))
.then(() => null);
});
}
getExtensionsReport(): Promise<IReportedExtension[]> {
return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.getExtensionsReport();
}
private getServer(extension: ILocalExtension): IExtensionManagementServer {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private _remoteAuthorityResolverPromise: Thenable<ResolvedAuthority>;
private hasToSyncExtensions(): Thenable<boolean> {
if (!this.extensionManagementServerService.remoteExtensionManagementServer) {
return Promise.resolve(false);
}
if (!this._remoteAuthorityResolverPromise) {
this._remoteAuthorityResolverPromise = this.remoteAuthorityResolverService.resolveAuthority(this.extensionManagementServerService.remoteExtensionManagementServer.authority);
}
return this._remoteAuthorityResolverPromise.then(({ syncExtensions }) => !!syncExtensions);
}
}

View File

@@ -2,19 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as sinon from 'sinon';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionEnablementService, DidUninstallExtensionEvent, EnablementState, IExtensionContributions, ILocalExtension, LocalExtensionType, DidInstallExtensionEvent, InstallOperation } 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';
import { StorageService, InMemoryLocalStorage } from 'vs/platform/storage/common/storageService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { TPromise } from 'vs/base/common/winjs.base';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
function storageService(instantiationService: TestInstantiationService): IStorageService {
let service = instantiationService.get(IStorageService);
@@ -25,18 +22,17 @@ function storageService(instantiationService: TestInstantiationService): IStorag
getWorkbenchState: () => WorkbenchState.FOLDER,
});
}
service = instantiationService.stub(IStorageService, instantiationService.createInstance(StorageService, new InMemoryLocalStorage(), new InMemoryLocalStorage()));
service = instantiationService.stub(IStorageService, new TestStorageService());
}
return service;
}
export class TestExtensionEnablementService extends ExtensionEnablementService {
constructor(instantiationService: TestInstantiationService) {
super(storageService(instantiationService), instantiationService.get(IWorkspaceContextService),
instantiationService.get(IEnvironmentService) || instantiationService.stub(IEnvironmentService, {} as IEnvironmentService),
instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService,
{ onDidUninstallExtension: new Emitter<DidUninstallExtensionEvent>().event } as IExtensionManagementService));
{ onDidInstallExtension: new Emitter<DidInstallExtensionEvent>().event, onDidUninstallExtension: new Emitter<DidUninstallExtensionEvent>().event } as IExtensionManagementService));
}
public async reset(): Promise<void> {
@@ -50,10 +46,11 @@ suite('ExtensionEnablementService Test', () => {
let testObject: IExtensionEnablementService;
const didUninstallEvent: Emitter<DidUninstallExtensionEvent> = new Emitter<DidUninstallExtensionEvent>();
const didInstallEvent: Emitter<DidInstallExtensionEvent> = new Emitter<DidInstallExtensionEvent>();
setup(() => {
instantiationService = new TestInstantiationService();
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => TPromise.as([]) } as IExtensionManagementService);
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([]) } as IExtensionManagementService);
testObject = new TestExtensionEnablementService(instantiationService);
});
@@ -298,6 +295,90 @@ suite('ExtensionEnablementService Test', () => {
.then(extensions => assert.deepEqual([], extensions));
});
test('test installing an extension re-eanbles it when disabled globally', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([], extensions);
});
test('test updating an extension does not re-eanbles it when disabled globally', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([{ id: 'pub.a' }], extensions);
});
test('test installing an extension fires enablement change event when disabled globally', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
return new Promise((c, e) => {
testObject.onEnablementChanged(e => {
if (e.id === local.galleryIdentifier.id) {
c();
}
});
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
});
});
test('test updating an extension does not fires enablement change event when disabled globally', async () => {
const target = sinon.spy();
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.Disabled);
testObject.onEnablementChanged(target);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
assert.ok(!target.called);
});
test('test installing an extension re-eanbles it when workspace disabled', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([], extensions);
});
test('test updating an extension does not re-eanbles it when workspace disabled', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
const extensions = await testObject.getDisabledExtensions();
assert.deepEqual([{ id: 'pub.a' }], extensions);
});
test('test installing an extension fires enablement change event when workspace disabled', async () => {
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
return new Promise((c, e) => {
testObject.onEnablementChanged(e => {
if (e.id === local.galleryIdentifier.id) {
c();
}
});
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
});
});
test('test updating an extension does not fires enablement change event when workspace disabled', async () => {
const target = sinon.spy();
const local = aLocalExtension('pub.a');
await testObject.setEnablement(local, EnablementState.WorkspaceDisabled);
testObject.onEnablementChanged(target);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Update });
assert.ok(!target.called);
});
test('test installing an extension should not fire enablement change event when extension is not disabled', async () => {
const target = sinon.spy();
const local = aLocalExtension('pub.a');
testObject.onEnablementChanged(target);
didInstallEvent.fire({ local, identifier: local.galleryIdentifier, operation: InstallOperation.Install });
assert.ok(!target.called);
});
test('test remove an extension from disablement list when uninstalled', () => {
return testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.WorkspaceDisabled)
.then(() => testObject.setEnablement(aLocalExtension('pub.a'), EnablementState.Disabled))
@@ -356,7 +437,7 @@ suite('ExtensionEnablementService Test', () => {
test('test getDisabledExtensions include extensions disabled in enviroment', () => {
instantiationService.stub(IEnvironmentService, { disableExtensions: ['pub.a'] } as IEnvironmentService);
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, getInstalled: () => TPromise.as([aLocalExtension('pub.a'), aLocalExtension('pub.b')]) } as IExtensionManagementService);
instantiationService.stub(IExtensionManagementService, { onDidUninstallExtension: didUninstallEvent.event, onDidInstallExtension: didInstallEvent.event, getInstalled: () => Promise.resolve([aLocalExtension('pub.a'), aLocalExtension('pub.b')]) } as IExtensionManagementService);
testObject = new TestExtensionEnablementService(instantiationService);
return testObject.getDisabledExtensions()
.then(actual => {
@@ -364,6 +445,7 @@ suite('ExtensionEnablementService Test', () => {
assert.equal(actual[0].id, 'pub.a');
});
});
});
function aLocalExtension(id: string, contributes?: IExtensionContributions): ILocalExtension {

View File

@@ -2,8 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as os from 'os';
import * as extfs from 'vs/base/node/extfs';