From 0f800b76cf6e88581dd32b919344296b5f92d8a3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 16 Dec 2015 08:34:08 +0000 Subject: [PATCH] load latest extension version fixes #1111 --- src/vs/base/common/arrays.ts | 4 ++ src/vs/platform/plugins/common/plugins.ts | 1 + .../electron-main/sharedProcessMain.ts | 6 ++- src/vs/workbench/node/extensionPoints.ts | 53 ++++++++++++------- src/vs/workbench/node/pluginHostMain.ts | 7 ++- .../parts/extensions/common/extensions.ts | 2 +- .../electron-browser/extensionsQuickOpen.ts | 4 +- .../extensions/node/extensionsService.ts | 36 +++++++++++-- 8 files changed, 83 insertions(+), 30 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 8a0cc1c5be9..fc4e544f3f8 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -220,3 +220,7 @@ export function commonPrefixLength(one: T[], other: T[], equals: (a: T, b: T) return result; } + +export function flatten(arr: T[][]): T[] { + return arr.reduce((r, v) => r.concat(v), []); +} \ No newline at end of file diff --git a/src/vs/platform/plugins/common/plugins.ts b/src/vs/platform/plugins/common/plugins.ts index d4ae4848dfb..8088557527e 100644 --- a/src/vs/platform/plugins/common/plugins.ts +++ b/src/vs/platform/plugins/common/plugins.ts @@ -11,6 +11,7 @@ import Severity from 'vs/base/common/severity'; export interface IPluginDescription { id: string; name: string; + version: string; publisher: string; isBuiltin: boolean; extensionFolderPath: string; diff --git a/src/vs/workbench/electron-main/sharedProcessMain.ts b/src/vs/workbench/electron-main/sharedProcessMain.ts index 269d0e06d11..72b586607ae 100644 --- a/src/vs/workbench/electron-main/sharedProcessMain.ts +++ b/src/vs/workbench/electron-main/sharedProcessMain.ts @@ -61,7 +61,11 @@ function main(server: Server, initData: IInitData): void { instantiationService.addSingleton(IRequestService, requestService); instantiationService.addSingleton(IExtensionsService, new SyncDescriptor(ExtensionsService)); - server.registerService('ExtensionService', instantiationService.getInstance(IExtensionsService)); + const extensionService = instantiationService.getInstance(IExtensionsService); + server.registerService('ExtensionService', extensionService); + + // eventually clean up old extensions + setTimeout(() => extensionService.removeDeprecatedExtensions(), 5000); } function setupIPC(hook: string): TPromise { diff --git a/src/vs/workbench/node/extensionPoints.ts b/src/vs/workbench/node/extensionPoints.ts index cf191c8bd7c..14bbd6d4b3f 100644 --- a/src/vs/workbench/node/extensionPoints.ts +++ b/src/vs/workbench/node/extensionPoints.ts @@ -5,19 +5,15 @@ 'use strict'; -import nls = require('vs/nls'); - -import fs = require('fs'); import pfs = require('vs/base/node/pfs'); - import {IPluginDescription} from 'vs/platform/plugins/common/plugins'; import {TPromise} from 'vs/base/common/winjs.base'; +import {groupBy, values} from 'vs/base/common/collections'; import paths = require('vs/base/common/paths'); import json = require('vs/base/common/json'); -import strings = require('vs/base/common/strings'); -import {ILanguageExtensionPoint} from 'vs/editor/common/modes/languageExtensionPoint'; -import {PluginsRegistry, IPluginsMessageCollector} from 'vs/platform/plugins/common/pluginsRegistry'; +import {IPluginsMessageCollector} from 'vs/platform/plugins/common/pluginsRegistry'; import {isValidPluginDescription} from 'vs/platform/plugins/node/pluginVersionValidator'; +import * as semver from 'semver'; const MANIFEST_FILE = 'package.json'; @@ -26,7 +22,13 @@ export class PluginScanner { /** * Scan the plugin defined in `absoluteFolderPath` */ - public static scanPlugin(version: string, collector: IPluginsMessageCollector, absoluteFolderPath:string, isBuiltin:boolean): TPromise { + public static scanPlugin( + version: string, + collector: IPluginsMessageCollector, + absoluteFolderPath:string, + isBuiltin:boolean + ) : TPromise + { absoluteFolderPath = paths.normalize(absoluteFolderPath); let builder = collector.scopeTo(absoluteFolderPath); let absoluteManifestPath = paths.join(absoluteFolderPath, MANIFEST_FILE); @@ -81,22 +83,37 @@ export class PluginScanner { /** * Scan a list of extensions defined in `absoluteFolderPath` */ - public static scanPlugins(version: string, collector: IPluginsMessageCollector, absoluteFolderPath:string, isBuiltin:boolean): TPromise { - return pfs.readDirsInDir(absoluteFolderPath).then((folders) => { - return TPromise.join( - folders.map((folder) => this.scanPlugin(version, collector, paths.join(absoluteFolderPath, folder), isBuiltin)) - ); - }, (err) => { - collector.error(absoluteFolderPath, err); - return []; - }).then((results) => results.filter(item => (item !== null))); + public static scanPlugins( + version: string, + collector: IPluginsMessageCollector, + absoluteFolderPath:string, + isBuiltin:boolean + ) : TPromise + { + return pfs.readDirsInDir(absoluteFolderPath) + .then(folders => TPromise.join(folders.map(f => this.scanPlugin(version, collector, paths.join(absoluteFolderPath, f), isBuiltin)))) + .then(plugins => plugins.filter(item => item !== null)) + .then(plugins => { + const pluginsById = values(groupBy(plugins, p => p.id)); + return pluginsById.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]); + }) + .then(null, err => { + collector.error(absoluteFolderPath, err); + return []; + }); } /** * Combination of scanPlugin and scanPlugins: If a plugin manifest is found at root, we load just this plugin, otherwise we assume * the folder contains multiple extensions. */ - public static scanOneOrMultiplePlugins(version: string, collector: IPluginsMessageCollector, absoluteFolderPath:string, isBuiltin:boolean): TPromise { + public static scanOneOrMultiplePlugins( + version: string, + collector: IPluginsMessageCollector, + absoluteFolderPath:string, + isBuiltin:boolean + ) : TPromise + { return pfs.fileExists(paths.join(absoluteFolderPath, MANIFEST_FILE)).then((exists) => { if (exists) { return this.scanPlugin(version, collector, absoluteFolderPath, isBuiltin).then((pluginDescription) => { diff --git a/src/vs/workbench/node/pluginHostMain.ts b/src/vs/workbench/node/pluginHostMain.ts index d8f2b947243..afaa971cbc4 100644 --- a/src/vs/workbench/node/pluginHostMain.ts +++ b/src/vs/workbench/node/pluginHostMain.ts @@ -162,10 +162,9 @@ export class PluginHostMain { } private static scanPlugins(collector: IPluginsMessageCollector, builtinPluginsPath: string, userInstallPath: string, pluginDevelopmentPath: string, version: string): TPromise { - - let builtinPlugins: TPromise = PluginScanner.scanPlugins(version, collector, builtinPluginsPath, true); - let userPlugins: TPromise = (userInstallPath ? PluginScanner.scanPlugins(version, collector, userInstallPath, false) : TPromise.as([])); - let developedPlugins: TPromise = (pluginDevelopmentPath ? PluginScanner.scanOneOrMultiplePlugins(version, collector, pluginDevelopmentPath, false) : TPromise.as([])); + const builtinPlugins = PluginScanner.scanPlugins(version, collector, builtinPluginsPath, true); + const userPlugins = !userInstallPath ? TPromise.as([]) : PluginScanner.scanPlugins(version, collector, userInstallPath, false); + const developedPlugins = !pluginDevelopmentPath ? TPromise.as([]) : PluginScanner.scanOneOrMultiplePlugins(version, collector, pluginDevelopmentPath, false); return TPromise.join([builtinPlugins, userPlugins, developedPlugins]).then((_: IPluginDescription[][]) => { let builtinPlugins = _[0]; diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/parts/extensions/common/extensions.ts index 11804925594..179e2640bf8 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/parts/extensions/common/extensions.ts @@ -50,5 +50,5 @@ export interface IExtensionsService { install(extension: IExtension): TPromise; install(zipPath: string): TPromise; uninstall(extension: IExtension): TPromise; - getInstalled(): TPromise; + getInstalled(includeDuplicateVersions?: boolean): TPromise; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts index 414717fae3b..bef6c8897d1 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen.ts @@ -244,10 +244,10 @@ class DataSource implements IDataSource { const extension = entry.extension; if (extension.galleryInformation) { - return extension.galleryInformation.id; + return `${ extension.galleryInformation.id }-${ extension.version }`; } - return `local@${ extension.publisher }.${extension.name}@${ extension.path || '' }`; + return `local@${ extension.publisher }.${ extension.name }-${ extension.version }@${ extension.path || '' }`; } getLabel(entry: IExtensionEntry): string { diff --git a/src/vs/workbench/parts/extensions/node/extensionsService.ts b/src/vs/workbench/parts/extensions/node/extensionsService.ts index defd83db302..a75c8fe8319 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsService.ts @@ -13,6 +13,7 @@ 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 { flatten } from 'vs/base/common/arrays'; 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'; @@ -22,6 +23,8 @@ import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common import { Limiter } from 'vs/base/common/async'; import Event, { Emitter } from 'vs/base/common/event'; import { UserSettings } from 'vs/workbench/node/userSettings'; +import * as semver from 'semver'; +import {groupBy, values} from 'vs/base/common/collections'; function parseManifest(raw: string): TPromise { return new Promise((c, e) => { @@ -119,7 +122,7 @@ export class ExtensionsService implements IExtensionsService { const url = galleryInformation.downloadUrl; const zipPath = path.join(tmpdir(), galleryInformation.id); - const extensionPath = path.join(this.extensionsPath, `${ extension.publisher }.${ extension.name }`); + const extensionPath = path.join(this.extensionsPath, `${ extension.publisher }.${ extension.name }-${ extension.version }`); const manifestPath = path.join(extensionPath, 'package.json'); const settings = TPromise.join([ @@ -162,11 +165,31 @@ export class ExtensionsService implements IExtensionsService { .then(() => this._onDidUninstallExtension.fire(extension)); } - public getInstalled(): TPromise { + public getInstalled(includeDuplicateVersions: boolean = false): TPromise { + const all = this.getAllInstalled(); + + if (includeDuplicateVersions) { + return all; + } + + return all.then(plugins => { + const byId = values(groupBy(plugins, p => `${ p.publisher }.${ p.name }`)); + return byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]); + }); + } + + private getDeprecated(): TPromise { + return this.getAllInstalled().then(plugins => { + const byId = values(groupBy(plugins, p => `${ p.publisher }.${ p.name }`)); + return flatten(byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version)).slice(1))); + }); + } + + private getAllInstalled(): TPromise { const limiter = new Limiter(10); return pfs.readdir(this.extensionsPath) - .then(extensions => Promise.join(extensions.map(e => { + .then(extensions => Promise.join(extensions.map(e => { const extensionPath = path.join(this.extensionsPath, e); return limiter.queue( @@ -180,6 +203,11 @@ export class ExtensionsService implements IExtensionsService { } private getInstallationPath(extension: IExtension): string { - return extension.path || path.join(this.extensionsPath, `${ extension.publisher }.${ extension.name }`); + return extension.path || path.join(this.extensionsPath, `${ extension.publisher }.${ extension.name }`); } + + public removeDeprecatedExtensions(): TPromise { + return this.getDeprecated() + .then(extensions => TPromise.join(extensions.filter(e => !!e.path).map(e => pfs.rimraf(e.path)))); + } } -- GitLab