提交 78097964 编写于 作者: S Sandeep Somavarapu

Implement #34812

上级 af1e604d
......@@ -5,16 +5,16 @@
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { distinct } from 'vs/base/common/arrays';
import { distinct, coalesce } from 'vs/base/common/arrays';
import Event, { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IExtensionManagementService, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { adoptToGalleryExtensionId, getIdAndVersionFromLocalExtensionId, 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';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensions/disabled';
const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled';
export class ExtensionEnablementService implements IExtensionEnablementService {
......@@ -22,8 +22,8 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
private disposables: IDisposable[] = [];
private _onEnablementChanged = new Emitter<string>();
public onEnablementChanged: Event<string> = this._onEnablementChanged.event;
private _onEnablementChanged = new Emitter<IExtensionIdentifier>();
public onEnablementChanged: Event<IExtensionIdentifier> = this._onEnablementChanged.event;
constructor(
@IStorageService private storageService: IStorageService,
......@@ -38,28 +38,28 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
}
public getGloballyDisabledExtensions(): string[] {
getGloballyDisabledExtensions(): IExtensionIdentifier[] {
return this.getDisabledExtensions(StorageScope.GLOBAL);
}
public getWorkspaceDisabledExtensions(): string[] {
getWorkspaceDisabledExtensions(): IExtensionIdentifier[] {
return this.getDisabledExtensions(StorageScope.WORKSPACE);
}
public canEnable(identifier: string): boolean {
canEnable(identifier: IExtensionIdentifier): boolean {
if (this.environmentService.disableExtensions) {
return false;
}
if (this.getGloballyDisabledExtensions().indexOf(identifier) !== -1) {
if (this.getGloballyDisabledExtensions().some(d => areSameExtensions(d, identifier))) {
return true;
}
if (this.getWorkspaceDisabledExtensions().indexOf(identifier) !== -1) {
if (this.getWorkspaceDisabledExtensions().some(d => areSameExtensions(d, identifier))) {
return true;
}
return false;
}
public setEnablement(identifier: string, enable: boolean, workspace: boolean = false): TPromise<boolean> {
setEnablement(identifier: IExtensionIdentifier, enable: boolean, workspace: boolean = false): TPromise<boolean> {
if (workspace && !this.hasWorkspace) {
return TPromise.wrapError<boolean>(new Error(localize('noWorkspace', "No workspace.")));
}
......@@ -83,10 +83,16 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
}
}
private disableExtension(identifier: string, scope: StorageScope): TPromise<boolean> {
migrateToIdentifiers(installed: IExtensionIdentifier[]): void {
this.migrateDisabledExtensions(installed, StorageScope.GLOBAL);
if (this.hasWorkspace) {
this.migrateDisabledExtensions(installed, StorageScope.WORKSPACE);
}
}
private disableExtension(identifier: IExtensionIdentifier, scope: StorageScope): TPromise<boolean> {
let disabledExtensions = this.getDisabledExtensions(scope);
const index = disabledExtensions.indexOf(identifier);
if (index === -1) {
if (disabledExtensions.every(e => !areSameExtensions(e, identifier))) {
disabledExtensions.push(identifier);
this.setDisabledExtensions(disabledExtensions, scope, identifier);
return TPromise.wrap(true);
......@@ -94,28 +100,30 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.wrap(false);
}
private enableExtension(identifier: string, scope: StorageScope, fireEvent = true): TPromise<boolean> {
private enableExtension(identifier: IExtensionIdentifier, scope: StorageScope, fireEvent = true): TPromise<boolean> {
let disabledExtensions = this.getDisabledExtensions(scope);
const index = disabledExtensions.indexOf(identifier);
if (index !== -1) {
disabledExtensions.splice(index, 1);
this.setDisabledExtensions(disabledExtensions, scope, identifier, fireEvent);
return TPromise.wrap(true);
for (let index = 0; index < disabledExtensions.length; index++) {
const disabledExtension = disabledExtensions[index];
if (areSameExtensions(disabledExtension, identifier)) {
disabledExtensions.splice(index, 1);
this.setDisabledExtensions(disabledExtensions, scope, identifier, fireEvent);
return TPromise.wrap(true);
}
}
return TPromise.wrap(false);
}
private getDisabledExtensions(scope: StorageScope): string[] {
private getDisabledExtensions(scope: StorageScope): IExtensionIdentifier[] {
if (scope === StorageScope.WORKSPACE && !this.hasWorkspace) {
return [];
}
const value = this.storageService.get(DISABLED_EXTENSIONS_STORAGE_PATH, scope, '');
return value ? distinct(value.split(',')).map(id => adoptToGalleryExtensionId(id)) : [];
return value ? JSON.parse(value) : [];
}
private setDisabledExtensions(disabledExtensions: string[], scope: StorageScope, extension: string, fireEvent = true): void {
private setDisabledExtensions(disabledExtensions: IExtensionIdentifier[], scope: StorageScope, extension: IExtensionIdentifier, fireEvent = true): void {
if (disabledExtensions.length) {
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions.join(','), scope);
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, JSON.stringify(disabledExtensions.map(({ id, uuid }) => (<IExtensionIdentifier>{ id, uuid }))), scope);
} else {
this.storageService.remove(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
......@@ -124,12 +132,28 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
}
}
private onDidUninstallExtension({ id, error }: DidUninstallExtensionEvent): void {
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 {
if (!error) {
id = getIdAndVersionFromLocalExtensionId(id).id;
const id = getIdAndVersionFromLocalExtensionId(identifier.id).id;
if (id) {
this.enableExtension(id, StorageScope.WORKSPACE, false);
this.enableExtension(id, StorageScope.GLOBAL, false);
const extension = { id, uuid: identifier.uuid };
this.enableExtension(extension, StorageScope.WORKSPACE, false);
this.enableExtension(extension, StorageScope.GLOBAL, false);
}
}
}
......
......@@ -135,10 +135,14 @@ export interface IGalleryExtensionAssets {
license: IGalleryExtensionAsset;
}
export interface IGalleryExtension {
uuid: string;
export interface IExtensionIdentifier {
id: string;
uuid?: string;
}
export interface IGalleryExtension {
name: string;
identifier: IExtensionIdentifier;
version: string;
date: string;
displayName: string;
......@@ -167,7 +171,7 @@ export enum LocalExtensionType {
export interface ILocalExtension {
type: LocalExtensionType;
id: string;
identifier: IExtensionIdentifier;
manifest: IExtensionManifest;
metadata: IGalleryMetadata;
path: string;
......@@ -217,19 +221,19 @@ export interface IExtensionGalleryService {
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): TPromise<void>;
getReadme(extension: IGalleryExtension): TPromise<string>;
getManifest(extension: IGalleryExtension): TPromise<IExtensionManifest>;
getChangelog(extension: IGalleryMetadata): TPromise<string>;
getChangelog(extension: IGalleryExtension): TPromise<string>;
loadCompatibleVersion(extension: IGalleryExtension): TPromise<IGalleryExtension>;
getAllDependencies(extension: IGalleryExtension): TPromise<IGalleryExtension[]>;
}
export interface InstallExtensionEvent {
id: string;
identifier: IExtensionIdentifier;
zipPath?: string;
gallery?: IGalleryExtension;
}
export interface DidInstallExtensionEvent {
id: string;
identifier: IExtensionIdentifier;
zipPath?: string;
gallery?: IGalleryExtension;
local?: ILocalExtension;
......@@ -237,7 +241,7 @@ export interface DidInstallExtensionEvent {
}
export interface DidUninstallExtensionEvent {
id: string;
identifier: IExtensionIdentifier;
error?: string;
}
......@@ -246,7 +250,7 @@ export interface IExtensionManagementService {
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<string>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
install(zipPath: string): TPromise<void>;
......@@ -264,24 +268,24 @@ export interface IExtensionEnablementService {
/**
* Event to listen on for extension enablement changes
*/
onEnablementChanged: Event<string>;
onEnablementChanged: Event<IExtensionIdentifier>;
/**
* Returns all globally disabled extension identifiers.
* Returns an empty array if none exist.
*/
getGloballyDisabledExtensions(): string[];
getGloballyDisabledExtensions(): IExtensionIdentifier[];
/**
* Returns all workspace disabled extension identifiers.
* Returns an empty array if none exist or workspace does not exist.
*/
getWorkspaceDisabledExtensions(): string[];
getWorkspaceDisabledExtensions(): IExtensionIdentifier[];
/**
* Returns `true` if given extension can be enabled by calling `setEnablement`, otherwise false`.
*/
canEnable(identifier: string): boolean;
canEnable(identifier: IExtensionIdentifier): boolean;
/**
* Enable or disable the given extension.
......@@ -292,7 +296,9 @@ export interface IExtensionEnablementService {
*
* Throws error if enablement is requested for workspace and there is no workspace
*/
setEnablement(identifier: string, enable: boolean, workspace?: boolean): TPromise<boolean>;
setEnablement(identifier: IExtensionIdentifier, enable: boolean, workspace?: boolean): TPromise<boolean>;
migrateToIdentifiers(installed: IExtensionIdentifier[]): void;
}
export const IExtensionTipsService = createDecorator<IExtensionTipsService>('extensionTipsService');
......
......@@ -7,7 +7,7 @@
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 } from './extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, LocalExtensionType, DidUninstallExtensionEvent, IExtensionIdentifier } from './extensionManagement';
import Event, { buffer } from 'vs/base/common/event';
export interface IExtensionManagementChannel extends IChannel {
......@@ -26,7 +26,7 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
onInstallExtension: Event<InstallExtensionEvent>;
onDidInstallExtension: Event<DidInstallExtensionEvent>;
onUninstallExtension: Event<string>;
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
constructor(private service: IExtensionManagementService) {
......@@ -63,8 +63,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
private _onDidInstallExtension = eventFromCall<DidInstallExtensionEvent>(this.channel, 'event:onDidInstallExtension');
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return this._onDidInstallExtension; }
private _onUninstallExtension = eventFromCall<string>(this.channel, 'event:onUninstallExtension');
get onUninstallExtension(): Event<string> { return this._onUninstallExtension; }
private _onUninstallExtension = eventFromCall<IExtensionIdentifier>(this.channel, 'event:onUninstallExtension');
get onUninstallExtension(): Event<IExtensionIdentifier> { return this._onUninstallExtension; }
private _onDidUninstallExtension = eventFromCall<DidUninstallExtensionEvent>(this.channel, 'event:onDidUninstallExtension');
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this._onDidUninstallExtension; }
......
......@@ -5,10 +5,13 @@
'use strict';
import { ILocalExtension, IGalleryExtension, IExtensionManifest, EXTENSION_IDENTIFIER_REGEX, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionEnablementService, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
export function areSameExtensions(a: { id: string }, b: { id: string }): boolean {
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
if (a.uuid && b.uuid) {
return a.uuid === b.uuid;
}
if (a.id === b.id) {
return true;
}
......@@ -19,14 +22,6 @@ export function getGalleryExtensionId(publisher: string, name: string): string {
return `${publisher}.${name.toLocaleLowerCase()}`;
}
export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string {
return getLocalExtensionId(extension.id, version);
}
export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): string {
return getLocalExtensionId(getGalleryExtensionId(manifest.publisher, manifest.name), manifest.version);
}
export function getGalleryExtensionIdFromLocal(local: ILocalExtension): string {
return getGalleryExtensionId(local.manifest.publisher, local.manifest.name);
}
......@@ -46,10 +41,6 @@ export function adoptToGalleryExtensionId(id: string): string {
return id.replace(EXTENSION_IDENTIFIER_REGEX, (match, publisher: string, name: string) => getGalleryExtensionId(publisher, name));
}
function getLocalExtensionId(id: string, version: string): string {
return `${id}-${version}`;
}
export function getLocalExtensionTelemetryData(extension: ILocalExtension): any {
return {
id: getGalleryExtensionIdFromLocal(extension),
......@@ -79,9 +70,9 @@ export function getLocalExtensionTelemetryData(extension: ILocalExtension): any
*/
export function getGalleryExtensionTelemetryData(extension: IGalleryExtension): any {
return {
id: extension.id,
id: extension.identifier.id,
name: extension.name,
galleryId: extension.uuid,
galleryId: extension.identifier.uuid,
publisherId: extension.publisherId,
publisherName: extension.publisher,
publisherDisplayName: extension.publisherDisplayName,
......@@ -102,9 +93,9 @@ export function getGloballyDisabledExtensions(extensionEnablementService: IExten
const globallyDisabled = extensionEnablementService.getGloballyDisabledExtensions();
if (!storageService.getBoolean(BetterMergeCheckKey, StorageScope.GLOBAL, false)) {
storageService.store(BetterMergeCheckKey, true);
if (globallyDisabled.indexOf(BetterMergeId) === -1 && installedExtensions.some(d => d.id === BetterMergeId)) {
globallyDisabled.push(BetterMergeId);
extensionEnablementService.setEnablement(BetterMergeId, false);
if (globallyDisabled.every(disabled => disabled.id !== BetterMergeId) && installedExtensions.some(d => d.id === BetterMergeId)) {
globallyDisabled.push({ id: BetterMergeId });
extensionEnablementService.setEnablement({ id: BetterMergeId }, false);
storageService.store(BetterMergeDisabledNowKey, true);
}
}
......
......@@ -245,8 +245,10 @@ function toExtension(galleryExtension: IRawGalleryExtension, extensionsGalleryUr
};
return {
uuid: galleryExtension.extensionId,
id: getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName),
identifier: {
id: getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName),
uuid: galleryExtension.extensionId
},
name: galleryExtension.extensionName,
version: version.version,
date: version.lastUpdated,
......@@ -488,7 +490,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
.withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished))
.withAssetTypes(AssetType.Manifest, AssetType.VSIX)
.withFilter(FilterType.ExtensionId, extension.uuid);
.withFilter(FilterType.ExtensionId, extension.identifier.uuid);
return this.queryGallery(query).then(({ galleryExtensions }) => {
const [rawExtension] = galleryExtensions;
......@@ -556,7 +558,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
dep.properties.dependencies.forEach(d => dependenciesSet.add(d));
}
}
result = distinct(result.concat(loadedDependencies), d => d.uuid);
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, root);
......
......@@ -18,9 +18,10 @@ import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
IGalleryExtension, IExtensionManifest, IGalleryMetadata,
InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, LocalExtensionType,
StatisticType
StatisticType,
IExtensionIdentifier
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getLocalExtensionIdFromGallery, getLocalExtensionIdFromManifest, getGalleryExtensionIdFromLocal, getIdAndVersionFromLocalExtensionId, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, getIdAndVersionFromLocalExtensionId, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId } 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';
......@@ -75,6 +76,7 @@ interface InstallableExtension {
zipPath: string;
id: string;
metadata: IGalleryMetadata;
current?: ILocalExtension;
}
export class ExtensionManagementService implements IExtensionManagementService {
......@@ -92,8 +94,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
private _onDidInstallExtension = new Emitter<DidInstallExtensionEvent>();
onDidInstallExtension: Event<DidInstallExtensionEvent> = this._onDidInstallExtension.event;
private _onUninstallExtension = new Emitter<string>();
onUninstallExtension: Event<string> = this._onUninstallExtension.event;
private _onUninstallExtension = new Emitter<IExtensionIdentifier>();
onUninstallExtension: Event<IExtensionIdentifier> = this._onUninstallExtension.event;
private _onDidUninstallExtension = new Emitter<DidUninstallExtensionEvent>();
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = this._onDidUninstallExtension.event;
......@@ -112,19 +114,19 @@ export class ExtensionManagementService implements IExtensionManagementService {
zipPath = path.resolve(zipPath);
return validate(zipPath).then<void>(manifest => {
const id = getLocalExtensionIdFromManifest(manifest);
const identifier = { id: getLocalExtensionIdFromManifest(manifest) };
return this.isObsolete(id).then(isObsolete => {
return this.isObsolete(identifier.id).then(isObsolete => {
if (isObsolete) {
return TPromise.wrapError(new Error(nls.localize('restartCodeLocal', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name)));
}
this._onInstallExtension.fire({ id, zipPath });
this._onInstallExtension.fire({ identifier, zipPath });
return this.installExtension({ zipPath, id, metadata: null })
return this.installExtension({ zipPath, id: identifier.id, metadata: null })
.then(
local => this._onDidInstallExtension.fire({ id, zipPath, local }),
error => { this._onDidInstallExtension.fire({ id, zipPath, error }); return TPromise.wrapError(error); }
local => this._onDidInstallExtension.fire({ identifier, zipPath, local }),
error => { this._onDidInstallExtension.fire({ identifier, zipPath, error }); return TPromise.wrapError(error); }
);
});
});
......@@ -155,11 +157,12 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise<ILocalExtension[]> {
return TPromise.join(extensions.map(extensionToInstall => this.downloadInstallableExtension(extensionToInstall)))
.then(
installableExtensions => TPromise.join(installableExtensions.map(installableExtension => this.installExtension(installableExtension)))
.then(null, error => this.rollback(extensions).then(() => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_LOCAL, error))),
error => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_GALLERY, error));
return this.getInstalled(LocalExtensionType.User)
.then(installed => TPromise.join(extensions.map(extensionToInstall => this.downloadInstallableExtension(extensionToInstall, installed)))
.then(
installableExtensions => TPromise.join(installableExtensions.map(installableExtension => this.installExtension(installableExtension)))
.then(null, error => this.rollback(extensions).then(() => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_LOCAL, error))),
error => this.onDidInstallExtensions(extensions, null, INSTALL_ERROR_GALLERY, error)));
}
private collectExtensionsToInstall(extension: IGalleryExtension): TPromise<IGalleryExtension[]> {
......@@ -174,37 +177,38 @@ export class ExtensionManagementService implements IExtensionManagementService {
.then(obsolete => obsolete.length ? TPromise.wrapError<IGalleryExtension[]>(new Error(nls.localize('restartCodeGallery', "Please restart Code before reinstalling."))) : extensionsToInstall);
}
private downloadInstallableExtension(extension: IGalleryExtension): TPromise<InstallableExtension> {
private downloadInstallableExtension(extension: IGalleryExtension, installed: ILocalExtension[]): TPromise<InstallableExtension> {
const current = installed.filter(i => i.identifier.uuid === extension.identifier.uuid)[0];
const id = getLocalExtensionIdFromGallery(extension, extension.version);
const metadata = <IGalleryMetadata>{
id: extension.uuid,
id: extension.identifier.uuid,
publisherId: extension.publisherId,
publisherDisplayName: extension.publisherDisplayName,
};
return this.galleryService.download(extension)
.then(zipPath => validate(zipPath).then(() => ({ zipPath, id, metadata })));
.then(zipPath => validate(zipPath).then(() => (<InstallableExtension>{ zipPath, id, metadata, current })));
}
private rollback(extensions: IGalleryExtension[]): TPromise<void> {
return this.filterOutUninstalled(extensions)
.then(installed => TPromise.join(installed.map(local => this.uninstallExtension(local.id))))
.then(installed => TPromise.join(installed.map(local => this.uninstallExtension(local.identifier))))
.then(() => null, () => null);
}
private onInstallExtensions(extensions: IGalleryExtension[]): void {
for (const extension of extensions) {
const id = getLocalExtensionIdFromGallery(extension, extension.version);
this._onInstallExtension.fire({ id, gallery: extension });
this._onInstallExtension.fire({ identifier: { id, uuid: extension.identifier.uuid }, gallery: extension });
}
}
private onDidInstallExtensions(extensions: IGalleryExtension[], local: ILocalExtension[], errorCode?: string, error?: any): TPromise<any> {
extensions.forEach((gallery, index) => {
const id = getLocalExtensionIdFromGallery(gallery, gallery.version);
const identifier = { id: getLocalExtensionIdFromGallery(gallery, gallery.version), uuid: gallery.identifier.uuid };
if (errorCode) {
this._onDidInstallExtension.fire({ id, gallery, error: errorCode });
this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode });
} else {
this._onDidInstallExtension.fire({ id, gallery, local: local[index] });
this._onDidInstallExtension.fire({ identifier, gallery, local: local[index] });
}
});
return error ? TPromise.wrapError(Array.isArray(error) ? this.joinErrors(error) : error) : TPromise.as(null);
......@@ -214,11 +218,11 @@ export class ExtensionManagementService implements IExtensionManagementService {
return this.getInstalled()
.then(local => {
return dependencies.filter(d => {
if (extension.uuid === d.uuid) {
if (extension.identifier.uuid === d.identifier.uuid) {
return false;
}
const extensionId = getLocalExtensionIdFromGallery(d, d.version);
return local.every(local => local.id !== extensionId);
return local.every(({ identifier }) => identifier.id !== extensionId);
});
});
}
......@@ -229,11 +233,11 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
private getGalleryExtensionForLocalExtension(galleryExtensions: IGalleryExtension[], localExtension: ILocalExtension): IGalleryExtension {
const filtered = galleryExtensions.filter(galleryExtension => getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version) === localExtension.id);
const filtered = galleryExtensions.filter(galleryExtension => areSameExtensions(localExtension.identifier, { id: getLocalExtensionIdFromGallery(galleryExtension, galleryExtension.version), uuid: galleryExtension.identifier.uuid }));
return filtered.length ? filtered[0] : null;
}
private installExtension({ zipPath, id, metadata }: InstallableExtension): TPromise<ILocalExtension> {
private installExtension({ zipPath, id, metadata, current }: InstallableExtension): TPromise<ILocalExtension> {
const extensionPath = path.join(this.extensionsPath, id);
return pfs.rimraf(extensionPath).then(() => {
......@@ -246,14 +250,16 @@ export class ExtensionManagementService implements IExtensionManagementService {
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 };
const local: ILocalExtension = { type, id, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
const local: ILocalExtension = { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
const manifestPath = path.join(extensionPath, 'package.json');
return pfs.readFile(manifestPath, 'utf8')
.then(raw => parseManifest(raw))
.then(({ manifest }) => assign(manifest, { __metadata: metadata }))
.then(manifest => pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')))
.then(() => this.checkForRename(current, local))
.then(() => local);
});
});
......@@ -273,6 +279,15 @@ export class ExtensionManagementService implements IExtensionManagementService {
.then(() => { /* drop resolved value */ });
}
private checkForRename(currentExtension: ILocalExtension, newExtension: ILocalExtension): TPromise<void> {
// Check if the gallery id for current and new exensions are same, if not, remove the current one.
if (currentExtension && getGalleryExtensionIdFromLocal(currentExtension) !== getGalleryExtensionIdFromLocal(newExtension)) {
// return this.uninstallExtension(currentExtension.identifier);
return this.setObsolete(currentExtension.identifier.id);
}
return TPromise.as(null);
}
private joinErrors(errors: (Error | string)[]): Error {
if (errors.length === 1) {
return errors[0] instanceof Error ? <Error>errors[0] : new Error(<string>errors[0]);
......@@ -350,7 +365,7 @@ export class ExtensionManagementService implements IExtensionManagementService {
if (dependents.length) {
return TPromise.wrapError<void>(new Error(this.getDependentsErrorMessage(extension, dependents)));
}
return TPromise.join([this.uninstallExtension(extension.id), ...dependenciesToUninstall.map(d => this.doUninstall(d))]).then(() => null);
return TPromise.join([this.uninstallExtension(extension.identifier), ...dependenciesToUninstall.map(d => this.doUninstall(d))]).then(() => null);
}
private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {
......@@ -401,7 +416,7 @@ export class ExtensionManagementService implements IExtensionManagementService {
private doUninstall(extension: ILocalExtension): TPromise<void> {
return this.preUninstallExtension(extension)
.then(() => this.uninstallExtension(extension.id))
.then(() => this.uninstallExtension(extension.identifier))
.then(() => this.postUninstallExtension(extension),
error => {
this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL);
......@@ -410,13 +425,13 @@ export class ExtensionManagementService implements IExtensionManagementService {
}
private preUninstallExtension(extension: ILocalExtension): TPromise<void> {
const extensionPath = path.join(this.extensionsPath, extension.id);
const extensionPath = path.join(this.extensionsPath, extension.identifier.id);
return pfs.exists(extensionPath)
.then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension"))))
.then(() => this._onUninstallExtension.fire(extension.id));
.then(() => this._onUninstallExtension.fire(extension.identifier));
}
private uninstallExtension(id: string): TPromise<void> {
private uninstallExtension({ id }: IExtensionIdentifier): TPromise<void> {
const extensionPath = path.join(this.extensionsPath, id);
return this.setObsolete(id)
.then(() => pfs.rimraf(extensionPath))
......@@ -428,7 +443,7 @@ export class ExtensionManagementService implements IExtensionManagementService {
await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall);
}
this._onDidUninstallExtension.fire({ id: extension.id, error });
this._onDidUninstallExtension.fire({ identifier: extension.identifier, error });
}
getInstalled(type: LocalExtensionType = null): TPromise<ILocalExtension[]> {
......@@ -474,7 +489,8 @@ export class ExtensionManagementService implements IExtensionManagementService {
if (manifest.extensionDependencies) {
manifest.extensionDependencies = manifest.extensionDependencies.map(id => adoptToGalleryExtensionId(id));
}
return { type, id, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
const identifier = { id, uuid: metadata ? metadata.id : null };
return { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl };
});
}).then(null, () => null);
......@@ -578,3 +594,15 @@ export class ExtensionManagementService implements IExtensionManagementService {
this.disposables = dispose(this.disposables);
}
}
export function getLocalExtensionIdFromGallery(extension: IGalleryExtension, version: string): string {
return getLocalExtensionId(extension.identifier.id, version);
}
export function getLocalExtensionIdFromManifest(manifest: IExtensionManifest): string {
return getLocalExtensionId(getGalleryExtensionId(manifest.publisher, manifest.name), manifest.version);
}
function getLocalExtensionId(id: string, version: string): string {
return `${id}-${version}`;
}
\ No newline at end of file
......@@ -69,7 +69,7 @@ suite('ExtensionEnablementService Test', () => {
});
test('test when no extensions are disabled for workspace when there is no workspace', (done) => {
testObject.setEnablement('pub.a', false, true)
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
......@@ -78,13 +78,13 @@ suite('ExtensionEnablementService Test', () => {
});
test('test disable an extension globally', (done) => {
testObject.setEnablement('pub.a', false)
.then(() => assert.deepEqual(['pub.a'], testObject.getGloballyDisabledExtensions()))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions()))
.then(done, done);
});
test('test disable an extension globally should return truthy promise', (done) => {
testObject.setEnablement('pub.a', false)
testObject.setEnablement({ id: 'pub.a' }, false)
.then(value => assert.ok(value))
.then(done, done);
});
......@@ -92,180 +92,180 @@ suite('ExtensionEnablementService Test', () => {
test('test disable an extension globally triggers the change event', (done) => {
const target = sinon.spy();
testObject.onEnablementChanged(target);
testObject.setEnablement('pub.a', false)
.then(() => assert.ok(target.calledWithExactly('pub.a')))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
});
test('test disable an extension globally again should return a falsy promise', (done) => {
testObject.setEnablement('pub.a', false)
.then(() => testObject.setEnablement('pub.a', false))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(value => assert.ok(!value))
.then(done, done);
});
test('test disable an extension for workspace', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => assert.deepEqual(['pub.a'], testObject.getWorkspaceDisabledExtensions()))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions()))
.then(done, done);
});
test('test disable an extension for workspace returns a truthy promise', (done) => {
testObject.setEnablement('pub.a', false, true)
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(value => assert.ok(value))
.then(done, done);
});
test('test disable an extension for workspace again should return a falsy promise', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', false, true))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(value => assert.ok(!value))
.then(done, done);
});
test('test disable an extension for workspace and then globally', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', false))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => {
assert.deepEqual(['pub.a'], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual(['pub.a'], testObject.getGloballyDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
});
test('test disable an extension for workspace and then globally return a truthy promise', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', false))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(value => assert.ok(value))
.then(done, done);
});
test('test disable an extension for workspace and then globally triggers the change event', (done) => {
const target = sinon.spy();
testObject.setEnablement('pub.a', false, true)
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement('pub.a', false))
.then(() => assert.ok(target.calledWithExactly('pub.a')))
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
});
test('test disable an extension globally and then for workspace', (done) => {
testObject.setEnablement('pub.a', false)
.then(() => testObject.setEnablement('pub.a', false, true))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(() => {
assert.deepEqual(['pub.a'], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual(['pub.a'], testObject.getGloballyDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
});
test('test disable an extension globally and then for workspace return a truthy promise', (done) => {
testObject.setEnablement('pub.a', false)
.then(() => testObject.setEnablement('pub.a', false, true))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(value => assert.ok(value))
.then(done, done);
});
test('test disable an extension globally and then for workspace triggers the change event', (done) => {
const target = sinon.spy();
testObject.setEnablement('pub.a', false)
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement('pub.a', false, true))
.then(() => assert.ok(target.calledWithExactly('pub.a')))
.then(() => testObject.setEnablement({ id: 'pub.a' }, false, true))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
});
test('test disable an extension for workspace when there is no workspace throws error', (done) => {
instantiationService.stub(IWorkspaceContextService, 'getWorkbenchState', WorkbenchState.EMPTY);
testObject.setEnablement('pub.a', false, true)
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => assert.fail('should throw an error'), error => assert.ok(error))
.then(done, done);
});
test('test enable an extension globally', (done) => {
testObject.setEnablement('pub.a', false)
.then(() => testObject.setEnablement('pub.a', true))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(() => assert.deepEqual([], testObject.getGloballyDisabledExtensions()))
.then(done, done);
});
test('test enable an extension globally return truthy promise', (done) => {
testObject.setEnablement('pub.a', false)
.then(() => testObject.setEnablement('pub.a', true))
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(value => assert.ok(value))
.then(done, done);
});
test('test enable an extension globally triggers change event', (done) => {
const target = sinon.spy();
testObject.setEnablement('pub.a', false)
testObject.setEnablement({ id: 'pub.a' }, false)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement('pub.a', true))
.then(() => assert.ok(target.calledWithExactly('pub.a')))
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.a' })))
.then(done, done);
});
test('test enable an extension globally when already enabled return falsy promise', (done) => {
testObject.setEnablement('pub.a', true)
testObject.setEnablement({ id: 'pub.a' }, true)
.then(value => assert.ok(!value))
.then(done, done);
});
test('test enable an extension for workspace', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', true, true))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true, true))
.then(() => assert.deepEqual([], testObject.getWorkspaceDisabledExtensions()))
.then(done, done);
});
test('test enable an extension for workspace return truthy promise', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', true, true))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, true, true))
.then(value => assert.ok(value))
.then(done, done);
});
test('test enable an extension for workspace triggers change event', (done) => {
const target = sinon.spy();
testObject.setEnablement('pub.b', false, true)
testObject.setEnablement({ id: 'pub.b' }, false, true)
.then(() => testObject.onEnablementChanged(target))
.then(() => testObject.setEnablement('pub.b', true, true))
.then(() => assert.ok(target.calledWithExactly('pub.b')))
.then(() => testObject.setEnablement({ id: 'pub.b' }, true, true))
.then(() => assert.ok(target.calledWithExactly({ id: 'pub.b' })))
.then(done, done);
});
test('test enable an extension for workspace when already enabled return falsy promise', (done) => {
testObject.setEnablement('pub.a', true, true)
testObject.setEnablement({ id: 'pub.a' }, true, true)
.then(value => assert.ok(!value))
.then(done, done);
});
test('test enable an extension for workspace when disabled in workspace and gloablly', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', false))
.then(() => testObject.setEnablement('pub.a', true, true))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => testObject.setEnablement({ id: 'pub.a' }, true, true))
.then(() => {
assert.deepEqual(['pub.a'], testObject.getGloballyDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getGloballyDisabledExtensions());
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
})
.then(done, done);
});
test('test enable an extension globally when disabled in workspace and gloablly', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', false))
.then(() => testObject.setEnablement('pub.a', true))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => testObject.setEnablement({ id: 'pub.a' }, true))
.then(() => {
assert.deepEqual(['pub.a'], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([{ id: 'pub.a' }], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([], testObject.getGloballyDisabledExtensions());
})
.then(done, done);
});
test('test remove an extension from disablement list when uninstalled', (done) => {
testObject.setEnablement('pub.a', false, true)
.then(() => testObject.setEnablement('pub.a', false))
.then(() => didUninstallEvent.fire({ id: 'pub.a-1.0.0' }))
testObject.setEnablement({ id: 'pub.a' }, false, true)
.then(() => testObject.setEnablement({ id: 'pub.a' }, false))
.then(() => didUninstallEvent.fire({ identifier: { id: 'pub.a-1.0.0' } }))
.then(() => {
assert.deepEqual([], testObject.getWorkspaceDisabledExtensions());
assert.deepEqual([], testObject.getGloballyDisabledExtensions());
......
......@@ -12,6 +12,7 @@ import { IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistr
export interface IExtensionDescription {
readonly id: string;
readonly name: string;
readonly uuid?: string;
readonly displayName?: string;
readonly version: string;
readonly publisher: string;
......
......@@ -436,7 +436,7 @@ export class EnableForWorkspaceAction extends Action implements IExtensionAction
private update(): void {
this.enabled = false;
if (this.extension) {
this.enabled = !this.extension.disabledGlobally && this.extension.disabledForWorkspace && this.extensionEnablementService.canEnable(this.extension.id);
this.enabled = !this.extension.disabledGlobally && this.extension.disabledForWorkspace && this.extensionEnablementService.canEnable(this.extension);
}
}
......@@ -475,7 +475,7 @@ export class EnableGloballyAction extends Action implements IExtensionAction {
private update(): void {
this.enabled = false;
if (this.extension) {
this.enabled = this.extension.disabledGlobally && this.extensionEnablementService.canEnable(this.extension.id);
this.enabled = this.extension.disabledGlobally && this.extensionEnablementService.canEnable(this.extension);
}
}
......@@ -531,7 +531,7 @@ export class EnableAction extends Action {
return;
}
this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.disabledGlobally || this.extension.disabledForWorkspace) && this.extensionEnablementService.canEnable(this.extension.id);
this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.disabledGlobally || this.extension.disabledForWorkspace) && this.extensionEnablementService.canEnable(this.extension);
this.class = this.enabled ? EnableAction.EnabledClass : EnableAction.DisabledClass;
}
......@@ -1536,7 +1536,7 @@ export class EnableAllAction extends Action {
}
private update(): void {
this.enabled = this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canEnable(e.id) && e.disabledGlobally);
this.enabled = this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canEnable(e) && e.disabledGlobally);
}
run(): TPromise<any> {
......@@ -1569,7 +1569,7 @@ export class EnableAllWorkpsaceAction extends Action {
}
private update(): void {
this.enabled = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canEnable(e.id) && !e.disabledGlobally && e.disabledForWorkspace);
this.enabled = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canEnable(e) && !e.disabledGlobally && e.disabledForWorkspace);
}
run(): TPromise<any> {
......
......@@ -29,6 +29,7 @@ export interface IExtension {
name: string;
displayName: string;
id: string;
uuid: string;
publisher: string;
publisherDisplayName: string;
version: string;
......
......@@ -12,7 +12,7 @@ import { onUnexpectedError, canceled } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, LocalExtensionType, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
......@@ -20,10 +20,10 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IMessageService, Severity, IChoiceService } from 'vs/platform/message/common/message';
import { Action } from 'vs/base/common/actions';
import { BetterMergeDisabledNowKey, BetterMergeId, getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { BetterMergeDisabledNowKey, BetterMergeId, getIdAndVersionFromLocalExtensionId, areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
export interface IExtensionStatus {
identifier: string;
identifier: IExtensionIdentifier;
local: ILocalExtension;
globallyEnabled: boolean;
}
......@@ -42,8 +42,8 @@ export class KeymapExtensions implements IWorkbenchContribution {
) {
this.disposables.push(
lifecycleService.onShutdown(() => this.dispose()),
instantiationService.invokeFunction(onExtensionChanged)((ids => {
TPromise.join(ids.map(id => this.checkForOtherKeymaps(id)))
instantiationService.invokeFunction(onExtensionChanged)((identifiers => {
TPromise.join(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))
.then(null, onUnexpectedError);
}))
);
......@@ -53,12 +53,12 @@ export class KeymapExtensions implements IWorkbenchContribution {
return 'vs.extensions.keymapExtensions';
}
private checkForOtherKeymaps(extensionId: string): TPromise<void> {
private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): TPromise<void> {
return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension));
const extension = arrays.first(keymaps, extension => extension.identifier === extensionId);
const extension = arrays.first(keymaps, extension => extension.identifier.id === extensionIdentifier.id);
if (extension && extension.globallyEnabled) {
const otherKeymaps = keymaps.filter(extension => extension.identifier !== extensionId && extension.globallyEnabled);
const otherKeymaps = keymaps.filter(extension => extension.identifier.id !== extensionIdentifier.id && extension.globallyEnabled);
if (otherKeymaps.length) {
return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);
}
......@@ -107,7 +107,7 @@ export class KeymapExtensions implements IWorkbenchContribution {
this.telemetryService.publicLog('disableOtherKeymaps', telemetryData);
if (confirmed) {
return TPromise.join(oldKeymaps.map(keymap => {
return this.extensionEnablementService.setEnablement(keymap.identifier, false);
return this.extensionEnablementService.setEnablement(keymap.local.identifier, false);
}));
}
return undefined;
......@@ -120,18 +120,18 @@ export class KeymapExtensions implements IWorkbenchContribution {
}
}
export function onExtensionChanged(accessor: ServicesAccessor): Event<string[]> {
export function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {
const extensionService = accessor.get(IExtensionManagementService);
const extensionEnablementService = accessor.get(IExtensionEnablementService);
return debounceEvent<string, string[]>(anyEvent(
return debounceEvent<IExtensionIdentifier, IExtensionIdentifier[]>(anyEvent(
chain(anyEvent(extensionService.onDidInstallExtension, extensionService.onDidUninstallExtension))
.map(e => stripVersion(e.id))
.map(e => ({ id: stripVersion(e.identifier.id), uuid: e.identifier.uuid }))
.event,
extensionEnablementService.onEnablementChanged
), (list, id) => {
if (!list) {
return [id];
} else if (list.indexOf(id) === -1) {
} else if (list.some(l => !areSameExtensions(l, id))) {
list.push(id);
}
return list;
......@@ -144,11 +144,10 @@ export function getInstalledExtensions(accessor: ServicesAccessor): TPromise<IEx
return extensionService.getInstalled().then(extensions => {
const globallyDisabled = extensionEnablementService.getGloballyDisabledExtensions();
return extensions.map(extension => {
const identifier = stripVersion(extension.id);
return {
identifier,
identifier: { id: adoptToGalleryExtensionId(extension.identifier.id), uuid: extension.identifier.uuid },
local: extension,
globallyEnabled: globallyDisabled.indexOf(identifier) === -1
globallyEnabled: globallyDisabled.every(disabled => !areSameExtensions(disabled, extension.identifier))
};
});
});
......@@ -156,7 +155,7 @@ export function getInstalledExtensions(accessor: ServicesAccessor): TPromise<IEx
export function isKeymapExtension(tipsService: IExtensionTipsService, extension: IExtensionStatus): boolean {
const cats = extension.local.manifest.categories;
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().indexOf(extension.identifier) !== -1;
return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().indexOf(extension.identifier.id) !== -1;
}
function stripVersion(id: string): string {
......@@ -192,7 +191,7 @@ export class BetterMergeDisabled implements IWorkbenchContribution {
outcome: 'uninstall',
});
return extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
return Promise.all(extensions.filter(e => stripVersion(e.id) === BetterMergeId)
return Promise.all(extensions.filter(e => stripVersion(e.identifier.id) === BetterMergeId)
.map(e => extensionManagementService.uninstall(e, true)));
});
}),
......
......@@ -20,9 +20,9 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionTipsService
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowService } from 'vs/platform/windows/common/windows';
......@@ -71,11 +71,15 @@ class Extension implements IExtension {
get id(): string {
if (this.gallery) {
return this.gallery.id;
return this.gallery.identifier.id;
}
return getGalleryExtensionIdFromLocal(this.local);
}
get uuid(): string {
return this.gallery ? this.gallery.identifier.uuid : this.local.identifier.uuid;
}
get publisher(): string {
return this.gallery ? this.gallery.publisher : this.local.manifest.publisher;
}
......@@ -365,14 +369,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
queryLocal(): TPromise<IExtension[]> {
return this.extensionService.getInstalled().then(result => {
const installedById = index(this.installed, e => e.local.id);
const installedById = index(this.installed, e => e.local.identifier.id);
const globallyDisabledExtensions = this.extensionEnablementService.getGloballyDisabledExtensions();
const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions();
this.installed = result.map(local => {
const extension = installedById[local.id] || new Extension(this.galleryService, this.stateProvider, local, null, this.telemetryService);
const extension = installedById[local.identifier.id] || new Extension(this.galleryService, this.stateProvider, local, null, this.telemetryService);
extension.local = local;
extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.id) !== -1;
extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.id) !== -1;
extension.disabledGlobally = globallyDisabledExtensions.some(d => areSameExtensions(d, extension));
extension.disabledForWorkspace = workspaceDisabledExtensions.some(d => areSameExtensions(d, extension));
return extension;
});
......@@ -423,7 +427,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
private fromGallery(gallery: IGalleryExtension): Extension {
const installed = this.installed.filter(installed => installed.id === gallery.id)[0];
const installed = this.getInstalledExtensionMatchingGallery(gallery);
if (installed) {
// Loading the compatible version only there is an engine property
......@@ -439,6 +443,21 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return new Extension(this.galleryService, this.stateProvider, null, gallery, this.telemetryService);
}
private getInstalledExtensionMatchingGallery(gallery: IGalleryExtension): Extension {
for (const installed of this.installed) {
if (installed.uuid) { // Installed from Gallery
if (installed.uuid === gallery.identifier.uuid) {
return installed;
}
} else {
if (installed.id === gallery.identifier.id) { // Installed from other sources
return installed;
}
}
}
return null;
}
private syncLocalWithGalleryExtension(local: Extension, gallery: IGalleryExtension) {
local.gallery = gallery;
this._onChange.fire();
......@@ -462,15 +481,26 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
private syncWithGallery(): TPromise<void> {
const names = this.installed
.filter(e => e.type === LocalExtensionType.User)
.map(e => e.id);
const ids = [], names = [];
for (const installed of this.installed) {
if (installed.type === LocalExtensionType.User) {
if (installed.uuid) {
ids.push(installed.uuid);
} else {
names.push(installed.id);
}
}
}
if (names.length === 0) {
return TPromise.as(null);
const promises = [];
if (ids.length) {
promises.push(this.queryGallery({ ids, pageSize: ids.length }));
}
if (names.length) {
promises.push(this.queryGallery({ names, pageSize: names.length }));
}
return this.queryGallery({ names, pageSize: names.length }) as TPromise<any>;
return TPromise.join(promises) as TPromise<any>;
}
private eventuallyAutoUpdateExtensions(): void {
......@@ -670,12 +700,12 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
private doSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise<boolean> {
if (workspace) {
return this.extensionEnablementService.setEnablement(extension.id, enable, workspace);
return this.extensionEnablementService.setEnablement(extension, enable, workspace);
}
const globalElablement = this.extensionEnablementService.setEnablement(extension.id, enable, false);
const globalElablement = this.extensionEnablementService.setEnablement(extension, enable, false);
if (enable && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
const workspaceEnablement = this.extensionEnablementService.setEnablement(extension.id, enable, true);
const workspaceEnablement = this.extensionEnablementService.setEnablement(extension, enable, true);
return TPromise.join([globalElablement, workspaceEnablement]).then(values => values[0] || values[1]);
}
return globalElablement;
......@@ -695,7 +725,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return;
}
let extension = this.installed.filter(e => e.id === gallery.id)[0];
let extension = this.installed.filter(e => areSameExtensions(e, gallery.identifier))[0];
if (!extension) {
extension = new Extension(this.galleryService, this.stateProvider, null, gallery, this.telemetryService);
......@@ -712,7 +742,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
private onDidInstallExtension(event: DidInstallExtensionEvent): void {
const { local, zipPath, error, gallery } = event;
const installing = gallery ? this.installing.filter(e => e.extension.id === gallery.id)[0] : null;
const installing = gallery ? this.installing.filter(e => areSameExtensions(e.extension, gallery.identifier))[0] : null;
const extension: Extension = installing ? installing.extension : zipPath ? new Extension(this.galleryService, this.stateProvider, null, null, this.telemetryService) : null;
if (extension) {
this.installing = installing ? this.installing.filter(e => e !== installing) : this.installing;
......@@ -738,9 +768,9 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
this._onChange.fire();
}
private onUninstallExtension(id: string): void {
const extension = this.installed.filter(e => e.local.id === id)[0];
const newLength = this.installed.filter(e => e.local.id !== id).length;
private onUninstallExtension({ id }: IExtensionIdentifier): void {
const extension = this.installed.filter(e => e.local.identifier.id === id)[0];
const newLength = this.installed.filter(e => e.local.identifier.id !== id).length;
// TODO: Ask @Joao why is this?
if (newLength === this.installed.length) {
return;
......@@ -748,19 +778,20 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
const start = new Date();
const operation = Operation.Uninstalling;
const uninstalling = this.uninstalling.filter(e => e.extension.local.id === id)[0] || { id, operation, extension, start };
this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => e.extension.local.id !== id)];
const uninstalling = this.uninstalling.filter(e => e.extension.local.identifier.id === id)[0] || { id, operation, extension, start };
this.uninstalling = [uninstalling, ...this.uninstalling.filter(e => e.extension.local.identifier.id !== id)];
this._onChange.fire();
}
private onDidUninstallExtension({ id, error }: DidUninstallExtensionEvent): void {
private onDidUninstallExtension({ identifier, error }: DidUninstallExtensionEvent): void {
const id = identifier.id;
if (!error) {
this.installed = this.installed.filter(e => e.local.id !== id);
this.installed = this.installed.filter(e => e.local.identifier.id !== id);
}
const uninstalling = this.uninstalling.filter(e => e.extension.local.id === id)[0];
this.uninstalling = this.uninstalling.filter(e => e.extension.local.id !== id);
const uninstalling = this.uninstalling.filter(e => e.extension.local.identifier.id === id)[0];
this.uninstalling = this.uninstalling.filter(e => e.extension.local.identifier.id !== id);
if (!uninstalling) {
return;
}
......@@ -772,19 +803,19 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
this._onChange.fire();
}
private onEnablementChanged(extensionIdentifier: string) {
const [extension] = this.local.filter(e => e.id === extensionIdentifier);
private onEnablementChanged(extensionIdentifier: IExtensionIdentifier) {
const [extension] = this.local.filter(e => areSameExtensions(e, extensionIdentifier));
if (extension) {
const globallyDisabledExtensions = this.extensionEnablementService.getGloballyDisabledExtensions();
const workspaceDisabledExtensions = this.extensionEnablementService.getWorkspaceDisabledExtensions();
extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.id) !== -1;
extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.id) !== -1;
extension.disabledGlobally = globallyDisabledExtensions.some(disabled => areSameExtensions(disabled, extension));
extension.disabledForWorkspace = workspaceDisabledExtensions.some(disabled => areSameExtensions(disabled, extension));
this._onChange.fire();
}
}
private getExtensionState(extension: Extension): ExtensionState {
if (extension.gallery && this.installing.some(e => e.extension.gallery && e.extension.gallery.id === extension.gallery.id)) {
if (extension.gallery && this.installing.some(e => e.extension.gallery && areSameExtensions(e.extension.gallery.identifier, extension.gallery.identifier))) {
return ExtensionState.Installing;
}
......@@ -792,7 +823,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return ExtensionState.Uninstalling;
}
const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && e.gallery.id === extension.gallery.id))[0];
const local = this.installed.filter(e => e === extension || (e.gallery && extension.gallery && areSameExtensions(e.gallery.identifier, extension.gallery.identifier)))[0];
return local ? ExtensionState.Installed : ExtensionState.Uninstalled;
}
......
......@@ -417,7 +417,7 @@ class WelcomePage {
extensionId: extensionSuggestion.id,
});
this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
const installedExtension = arrays.first(extensions, extension => extension.identifier === extensionSuggestion.id);
const installedExtension = arrays.first(extensions, extension => extension.identifier.id === extensionSuggestion.id);
if (installedExtension && installedExtension.globallyEnabled) {
/* __GDPR__FRAGMENT__
"WelcomePageInstalled-1" : {
......@@ -443,7 +443,7 @@ class WelcomePage {
return this.extensionManagementService.installFromGallery(extension)
.then(() => {
// TODO: Do this as part of the install to avoid multiple events.
return this.extensionEnablementService.setEnablement(extensionSuggestion.id, false);
return this.extensionEnablementService.setEnablement({ id: extensionSuggestion.id }, false);
}).then(() => {
return true;
});
......@@ -466,7 +466,7 @@ class WelcomePage {
return foundAndInstalled.then(found => {
messageDelay.cancel();
if (found) {
return this.extensionEnablementService.setEnablement(extensionSuggestion.id, true)
return this.extensionEnablementService.setEnablement({ id: extensionSuggestion.id }, true)
.then(() => {
/* __GDPR__FRAGMENT__
"WelcomePageInstalled-2" : {
......
......@@ -59,7 +59,12 @@ class ExtensionManifestParser extends ExtensionManifestHandler {
public parse(): TPromise<IExtensionDescription> {
return pfs.readFile(this._absoluteManifestPath).then((manifestContents) => {
try {
return JSON.parse(manifestContents.toString());
const manifest = JSON.parse(manifestContents.toString());
if (manifest.__metadata) {
manifest.uuid = manifest.__metadata.id;
}
delete manifest.__metadata;
return manifest;
} catch (e) {
this._log.error(this._absoluteFolderPath, nls.localize('jsonParseFail', "Failed to parse {0}: {1}.", this._absoluteManifestPath, getParseErrorMessage(e.message)));
}
......
......@@ -295,6 +295,10 @@ export class ExtensionService implements IExtensionService {
});
ExtensionService._scanInstalledExtensions(this._environmentService, log).then((installedExtensions) => {
// Migrate enablement service to use identifiers
this._extensionEnablementService.migrateToIdentifiers(installedExtensions);
const disabledExtensions = [
...getGloballyDisabledExtensions(this._extensionEnablementService, this._storageService, installedExtensions),
...this._extensionEnablementService.getWorkspaceDisabledExtensions()
......@@ -314,7 +318,7 @@ export class ExtensionService implements IExtensionService {
if (disabledExtensions.length === 0) {
return installedExtensions;
}
return installedExtensions.filter(e => disabledExtensions.every(id => !areSameExtensions({ id }, e)));
return installedExtensions.filter(e => disabledExtensions.every(disabled => !areSameExtensions(disabled, e)));
}).then((extensionDescriptions) => {
this._registry = new ExtensionDescriptionRegistry(extensionDescriptions);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册