/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import nls = require('vs/nls'); import { tmpdir } from 'os'; import * as path from 'path'; import types = require('vs/base/common/types'); import { ServiceEvent } from 'vs/base/common/service'; import errors = require('vs/base/common/errors'); import * as pfs from 'vs/base/node/pfs'; import { assign } from 'vs/base/common/objects'; import { extract, buffer } from 'vs/base/node/zip'; import { Promise, TPromise } from 'vs/base/common/winjs.base'; import { IExtensionsService, IExtension, IExtensionManifest, IGalleryInformation } from 'vs/workbench/parts/extensions/common/extensions'; import { download } from 'vs/base/node/request'; import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService'; import { Limiter } from 'vs/base/common/async'; import Event, { Emitter } from 'vs/base/common/event'; function parseManifest(raw: string): TPromise { return new Promise((c, e) => { try { c(JSON.parse(raw)); } catch (err) { e(new Error(nls.localize('invalidManifest', "Extension invalid: package.json is not a JSON file."))); } }); } function validate(zipPath: string, extension?: IExtension): TPromise { return buffer(zipPath, 'extension/package.json') .then(buffer => parseManifest(buffer.toString('utf8'))) .then(manifest => { if (extension) { if (extension.name !== manifest.name) { return Promise.wrapError(Error(nls.localize('invalidName', "Extension invalid: manifest name mismatch."))); } if (extension.publisher !== manifest.publisher) { return Promise.wrapError(Error(nls.localize('invalidPublisher', "Extension invalid: manifest publisher mismatch."))); } if (extension.version !== manifest.version) { return Promise.wrapError(Error(nls.localize('invalidVersion', "Extension invalid: manifest version mismatch."))); } } return Promise.as(manifest); }); } function createExtension(manifest: IExtensionManifest, galleryInformation?: IGalleryInformation, path?: string): IExtension { const extension: IExtension = { name: manifest.name, displayName: manifest.displayName || manifest.name, publisher: manifest.publisher, version: manifest.version, description: manifest.description || '' }; if (galleryInformation) { extension.galleryInformation = galleryInformation; } if (path) { extension.path = path; } return extension; } export class ExtensionsService implements IExtensionsService { public serviceId = IExtensionsService; private extensionsPath: string; private _onInstallExtension = new Emitter(); @ServiceEvent onInstallExtension = this._onInstallExtension.event; private _onDidInstallExtension = new Emitter(); @ServiceEvent onDidInstallExtension = this._onDidInstallExtension.event; private _onUninstallExtension = new Emitter(); @ServiceEvent onUninstallExtension = this._onUninstallExtension.event; private _onDidUninstallExtension = new Emitter(); @ServiceEvent onDidUninstallExtension = this._onDidUninstallExtension.event; constructor( @IWorkspaceContextService private contextService: IWorkspaceContextService ) { const env = contextService.getConfiguration().env; this.extensionsPath = env.userPluginsHome; } public install(extension: IExtension): TPromise; public install(zipPath: string): TPromise; public install(arg: any): TPromise { if (types.isString(arg)) { return this.installFromZip(arg); } return this.installFromGallery(arg); } private installFromGallery(extension: IExtension): TPromise { const galleryInformation = extension.galleryInformation; if (!galleryInformation) { return TPromise.wrapError(new Error(nls.localize('missingGalleryInformation', "Gallery information is missing"))); } const url = `${ galleryInformation.galleryApiUrl }/publisher/${ extension.publisher }/extension/${ extension.name }/latest/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage?install=true`; const zipPath = path.join(tmpdir(), galleryInformation.id); const extensionPath = path.join(this.extensionsPath, `${ extension.publisher }.${ extension.name }`); const manifestPath = path.join(extensionPath, 'package.json'); return download(zipPath, { url }) .then(() => validate(zipPath, extension)) .then(manifest => { this._onInstallExtension.fire(manifest); return manifest; }) .then(manifest => extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true }).then(() => manifest)) .then(manifest => { manifest = assign({ __metadata: galleryInformation }, manifest); return pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')); }) .then(() => { this._onDidInstallExtension.fire(extension); return extension; }); } private installFromZip(zipPath: string): TPromise { return validate(zipPath).then(manifest => { const extensionPath = path.join(this.extensionsPath, `${ manifest.publisher }.${ manifest.name }`); this._onInstallExtension.fire(manifest); return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true }) .then(() => createExtension(manifest, ( manifest).__metadata, extensionPath)) .then(extension => { this._onDidInstallExtension.fire(extension); return extension; }); }); } public uninstall(extension: IExtension): TPromise { const extensionPath = this.getInstallationPath(extension); return pfs.exists(extensionPath) .then(exists => exists ? null : Promise.wrapError(new Error(nls.localize('notExists', "Could not find extension")))) .then(() => this._onUninstallExtension.fire(extension)) .then(() => pfs.rimraf(extensionPath)) .then(() => this._onDidUninstallExtension.fire(extension)); } public getInstalled(): TPromise { const limiter = new Limiter(10); return pfs.readdir(this.extensionsPath) .then(extensions => Promise.join(extensions.map(e => { const extensionPath = path.join(this.extensionsPath, e); return limiter.queue( () => pfs.readFile(path.join(extensionPath, 'package.json'), 'utf8') .then(raw => parseManifest(raw)) .then(manifest => createExtension(manifest, ( manifest).__metadata, extensionPath)) .then(null, () => null) ); }))) .then(result => result.filter(a => !!a)); } private getInstallationPath(extension: IExtension): string { return extension.path || path.join(this.extensionsPath, `${ extension.publisher }.${ extension.name }`); } }