提交 9f492373 编写于 作者: S Sandeep Somavarapu

- Use URI to install from vsix

- Implement zip and unzip extensions APIs
- Sync extension across management servers
- Download service to download zips
上级 1f1b7781
......@@ -29,6 +29,7 @@
"fast-plist": "0.1.2",
"gc-signals": "^0.0.1",
"getmac": "1.4.1",
"glob": "^5.0.13",
"graceful-fs": "4.1.11",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.1",
......@@ -51,7 +52,8 @@
"vscode-textmate": "^4.0.1",
"vscode-xterm": "3.7.0-beta4",
"winreg": "^1.2.4",
"yauzl": "^2.9.1"
"yauzl": "^2.9.1",
"yazl": "^2.4.3"
},
"devDependencies": {
"7zip": "0.0.6",
......@@ -71,7 +73,6 @@
"eslint": "^3.4.0",
"event-stream": "^3.1.7",
"express": "^4.13.1",
"glob": "^5.0.13",
"gulp": "^3.8.9",
"gulp-atom-electron": "^1.16.1",
"gulp-azure-storage": "^0.7.0",
......
// Generated by typings
// Source: https://raw.githubusercontent.com/typed-typings/npm-minimatch/74f47de8acb42d668491987fc6bc144e7d9aa891/minimatch.d.ts
declare module '~glob~minimatch/minimatch' {
function minimatch (target: string, pattern: string, options?: minimatch.Options): boolean;
namespace minimatch {
export function match (list: string[], pattern: string, options?: Options): string[];
export function filter (pattern: string, options?: Options): (element: string, indexed: number, array: string[]) => boolean;
export function makeRe (pattern: string, options?: Options): RegExp;
/**
* All options are `false` by default.
*/
export interface Options {
/**
* Dump a ton of stuff to stderr.
*/
debug?: boolean;
/**
* Do not expand `{a,b}` and `{1..3}` brace sets.
*/
nobrace?: boolean;
/**
* Disable `**` matching against multiple folder names.
*/
noglobstar?: boolean;
/**
* Allow patterns to match filenames starting with a period, even if the pattern does not explicitly have a period in that spot.
*
* Note that by default, `a\/**\/b` will not match `a/.d/b`, unless `dot` is set.
*/
dot?: boolean;
/**
* Disable "extglob" style patterns like `+(a|b)`.
*/
noext?: boolean;
/**
* Perform a case-insensitive match.
*/
nocase?: boolean;
/**
* When a match is not found by `minimatch.match`, return a list containing the pattern itself if this option is set. When not set, an empty list is returned if there are no matches.
*/
nonull?: boolean;
/**
* If set, then patterns without slashes will be matched against the basename of the path if it contains slashes. For example, `a?b` would match the path `/xyz/123/acb`, but not `/xyz/acb/123`.
*/
matchBase?: boolean;
/**
* Suppress the behavior of treating `#` at the start of a pattern as a comment.
*/
nocomment?: boolean;
/**
* Suppress the behavior of treating a leading `!` character as negation.
*/
nonegate?: boolean;
/**
* Returns from negate expressions the same as if they were not negated. (Ie, true on a hit, false on a miss.)
*/
flipNegate?: boolean;
}
export class Minimatch {
constructor (pattern: string, options?: Options);
/**
* The original pattern the minimatch object represents.
*/
pattern: string;
/**
* The options supplied to the constructor.
*/
options: Options;
/**
* Created by the `makeRe` method. A single regular expression expressing the entire pattern. This is useful in cases where you wish to use the pattern somewhat like `fnmatch(3)` with `FNM_PATH` enabled.
*/
regexp: RegExp;
/**
* True if the pattern is negated.
*/
negate: boolean;
/**
* True if the pattern is a comment.
*/
comment: boolean;
/**
* True if the pattern is `""`.
*/
empty: boolean;
/**
* Generate the regexp member if necessary, and return it. Will return false if the pattern is invalid.
*/
makeRe (): RegExp | boolean;
/**
* Return true if the filename matches the pattern, or false otherwise.
*/
match (fname: string): boolean;
/**
* Take a `/-`split filename, and match it against a single row in the `regExpSet`. This method is mainly for internal use, but is exposed so that it can be used by a glob-walker that needs to avoid excessive filesystem calls.
*/
matchOne (fileArray: string[], patternArray: string[], partial: boolean): boolean;
}
}
export = minimatch;
}
declare module '~glob~minimatch' {
import main = require('~glob~minimatch/minimatch');
export = main;
}
// Generated by typings
// Source: https://raw.githubusercontent.com/types/npm-glob/59ca0f5d4696a8d4da27858035316c1014133fcb/glob.d.ts
declare module '~glob/glob' {
import events = require('events');
import fs = require('fs');
import minimatch = require('~glob~minimatch');
function glob (pattern: string, cb: (err: Error, matches: string[]) => void): void;
function glob (pattern: string, options: glob.Options, cb: (err: Error, matches: string[]) => void): void;
namespace glob {
export function sync (pattern: string, options?: Options): string[];
export function hasMagic (pattern: string, options?: Options): boolean;
export interface Cache {
[path: string]: boolean | string | string[];
}
export interface StatCache {
[path: string]: fs.Stats;
}
export interface Symlinks {
[path: string]: boolean;
}
export interface Options extends minimatch.Options {
/**
* The current working directory in which to search. Defaults to `process.cwd()`.
*/
cwd?: string;
/**
* The place where patterns starting with `/` will be mounted onto. Defaults to `path.resolve(options.cwd, "/")` (`/` on Unix systems, and `C:\` or some such on Windows.)
*/
root?: string;
/**
* Include `.dot` files in normal matches and `globstar` matches. Note that an explicit dot in a portion of the pattern will always match dot files.
*/
dot?: boolean;
/**
* By default, a pattern starting with a forward-slash will be "mounted" onto the root setting, so that a valid filesystem path is returned. Set this flag to disable that behavior.
*/
nomount?: boolean;
/**
* Add a `/` character to directory matches. Note that this requires additional stat calls.
*/
mark?: boolean;
/**
* Don't sort the results.
*/
nosort?: boolean;
/**
* Set to true to stat all results. This reduces performance somewhat, and is completely unnecessary, unless `readdir` is presumed to be an untrustworthy indicator of file existence.
*/
stat?: boolean;
/**
* When an unusual error is encountered when attempting to read a directory, a warning will be printed to stderr. Set the `silent` option to true to suppress these warnings.
*/
silent?: boolean;
/**
* When an unusual error is encountered when attempting to read a directory, the process will just continue on in search of other matches. Set the `strict` option to raise an error in these cases.
*/
strict?: boolean;
/**
* See `cache` property above. Pass in a previously generated cache object to save some fs calls.
*/
cache?: Cache;
/**
* A cache of results of filesystem information, to prevent unnecessary stat calls. While it should not normally be necessary to set this, you may pass the statCache from one glob() call to the options object of another, if you know that the filesystem will not change between calls. (See https://github.com/isaacs/node-glob#race-conditions)
*/
statCache?: StatCache;
/**
* A cache of known symbolic links. You may pass in a previously generated `symlinks` object to save lstat calls when resolving `**` matches.
*/
symlinks?: Symlinks;
/**
* DEPRECATED: use `glob.sync(pattern, opts)` instead.
*/
sync?: boolean;
/**
* In some cases, brace-expanded patterns can result in the same file showing up multiple times in the result set. By default, this implementation prevents duplicates in the result set. Set this flag to disable that behavior.
*/
nounique?: boolean;
/**
* Set to never return an empty set, instead returning a set containing the pattern itself. This is the default in glob(3).
*/
nonull?: boolean;
/**
* Set to enable debug logging in minimatch and glob.
*/
debug?: boolean;
/**
* Do not expand `{a,b}` and `{1..3}` brace sets.
*/
nobrace?: boolean;
/**
* Do not match `**` against multiple filenames. (Ie, treat it as a normal `*` instead.)
*/
noglobstar?: boolean;
/**
* Do not match `+(a|b)` "extglob" patterns.
*/
noext?: boolean;
/**
* Perform a case-insensitive match. Note: on case-insensitive filesystems, non-magic patterns will match by default, since `stat` and `readdir` will not raise errors.
*/
nocase?: boolean;
/**
* Perform a basename-only match if the pattern does not contain any slash characters. That is, `*.js` would be treated as equivalent to `**\/*.js`, matching all js files in all directories.
*/
matchBase?: any;
/**
* Do not match directories, only files. (Note: to match only directories, simply put a `/` at the end of the pattern.)
*/
nodir?: boolean;
/**
* Add a pattern or an array of glob patterns to exclude matches. Note: `ignore` patterns are always in `dot:true` mode, regardless of any other settings.
*/
ignore?: string | string[];
/**
* Follow symlinked directories when expanding `**` patterns. Note that this can result in a lot of duplicate references in the presence of cyclic links.
*/
follow?: boolean;
/**
* Set to true to call `fs.realpath` on all of the results. In the case of a symlink that cannot be resolved, the full absolute path to the matched entry is returned (though it will usually be a broken symlink)
*/
realpath?: boolean;
}
export class Glob extends events.EventEmitter {
constructor (pattern: string, cb?: (err: Error, matches: string[]) => void);
constructor (pattern: string, options: Options, cb?: (err: Error, matches: string[]) => void);
/**
* The minimatch object that the glob uses.
*/
minimatch: minimatch.Minimatch;
/**
* The options object passed in.
*/
options: Options;
/**
* Boolean which is set to true when calling `abort()`. There is no way at this time to continue a glob search after aborting, but you can re-use the statCache to avoid having to duplicate syscalls.
* @type {boolean}
*/
aborted: boolean;
/**
* Convenience object.
*/
cache: Cache;
/**
* Cache of `fs.stat` results, to prevent statting the same path multiple times.
*/
statCache: StatCache;
/**
* A record of which paths are symbolic links, which is relevant in resolving `**` patterns.
*/
symlinks: Symlinks;
/**
* An optional object which is passed to `fs.realpath` to minimize unnecessary syscalls. It is stored on the instantiated Glob object, and may be re-used.
*/
realpathCache: { [path: string]: string };
found: string[];
/**
* Temporarily stop the search.
*/
pause(): void;
/**
* Resume the search.
*/
resume(): void;
/**
* Stop the search forever.
*/
abort(): void;
}
}
export = glob;
}
declare module 'glob/glob' {
import main = require('~glob/glob');
export = main;
}
declare module 'glob' {
import main = require('~glob/glob');
export = main;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'yazl' {
import * as stream from 'stream';
class ZipFile {
outputStream: stream.Stream;
addBuffer(buffer: Buffer, path: string);
addFile(localPath: string, path: string);
end();
}
}
\ No newline at end of file
......@@ -11,6 +11,7 @@ import { nfcall, ninvoke, SimpleThrottler } from 'vs/base/common/async';
import { mkdirp, rimraf } from 'vs/base/node/pfs';
import { TPromise } from 'vs/base/common/winjs.base';
import { open as _openZip, Entry, ZipFile } from 'yauzl';
import * as yazl from 'yazl';
import { ILogService } from 'vs/platform/log/common/log';
export interface IExtractOptions {
......@@ -151,6 +152,27 @@ function openZip(zipFile: string, lazy: boolean = false): TPromise<ZipFile> {
.then(null, err => TPromise.wrapError(toExtractError(err)));
}
export interface IFile {
path: string;
contents?: Buffer | string;
localPath?: string;
}
export function zip(zipPath: string, files: IFile[]): TPromise<string> {
return new TPromise<string>((c, e) => {
const zip = new yazl.ZipFile();
files.forEach(f => f.contents ? zip.addBuffer(typeof f.contents === 'string' ? new Buffer(f.contents, 'utf8') : f.contents, f.path) : zip.addFile(f.localPath, f.path));
zip.end();
const zipStream = createWriteStream(zipPath);
zip.outputStream.pipe(zipStream);
zip.outputStream.once('error', e);
zipStream.once('error', e);
zipStream.once('finish', () => c(zipPath));
});
}
export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, logService: ILogService): TPromise<void> {
const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : '');
......
......@@ -43,6 +43,8 @@ import { LocalizationsChannel } from 'vs/platform/localizations/common/localizat
import { DialogChannelClient } from 'vs/platform/dialogs/common/dialogIpc';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDownloadService } from 'vs/platform/download/common/download';
import { DownloadServiceChannelClient } from 'vs/platform/download/node/downloadIpc';
export interface ISharedProcessConfiguration {
readonly machineId: string;
......@@ -84,9 +86,13 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I
const activeWindowManager = new ActiveWindowManager(windowsService);
const route = () => activeWindowManager.getActiveClientId();
const dialogChannel = server.getChannel('dialog', { routeCall: route, routeEvent: route });
services.set(IDialogService, new DialogChannelClient(dialogChannel));
const downloadChannel = server.getChannel('download', { routeCall: route, routeEvent: route });
services.set(IDownloadService, new DownloadServiceChannelClient(downloadChannel));
const instantiationService = new InstantiationService(services);
instantiationService.invokeFunction(accessor => {
......
......@@ -41,6 +41,9 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
import { areSameExtensions, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri';
import { IDownloadService } from 'vs/platform/download/common/download';
import { DownloadService } from 'vs/platform/download/node/download';
const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id);
const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id);
......@@ -101,7 +104,7 @@ class Main {
.map(id => () => {
const extension = path.isAbsolute(id) ? id : path.join(process.cwd(), id);
return this.extensionManagementService.install(extension).then(() => {
return this.extensionManagementService.install(URI.file(extension)).then(() => {
console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension)));
}, error => {
if (isPromiseCanceledError(error)) {
......@@ -240,6 +243,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
services.set(IDownloadService, new SyncDescriptor(DownloadService));
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentPath && !envService.args['disable-telemetry'] && product.enableTelemetry) {
......
/*---------------------------------------------------------------------------------------------
* 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 URI from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TPromise } from 'vs/base/common/winjs.base';
export const IDownloadService = createDecorator<IDownloadService>('downloadService');
export interface IDownloadService {
_serviceBrand: any;
download(location: URI, file: string): TPromise<void>;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDownloadService } from 'vs/platform/download/common/download';
export class DownloadService implements IDownloadService {
_serviceBrand: any;
download(from: URI, to: string): TPromise<void> {
throw new Error('Not supported');
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 URI from 'vs/base/common/uri';
import * as path from 'path';
import * as fs from 'fs';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event, Emitter, buffer } from 'vs/base/common/event';
import { IDownloadService } from 'vs/platform/download/common/download';
import { mkdirp } from 'vs/base/node/pfs';
import { onUnexpectedError } from 'vs/base/common/errors';
export type UploadResponse = Buffer | Error | undefined;
export function upload(uri: URI): Event<UploadResponse> {
const stream = new Emitter<Buffer | Error | undefined>();
fs.open(uri.fsPath, 'r', (err, fd) => {
if (err) {
if (err.code === 'ENOENT') {
stream.fire(new Error('File Not Found'));
}
return;
}
const finish = (err?: any) => {
if (err) {
stream.fire(err);
} else {
stream.fire();
if (fd) {
fs.close(fd, err => {
if (err) {
onUnexpectedError(err);
}
});
}
}
};
let currentPosition: number = 0;
const readChunk = () => {
const chunkBuffer = Buffer.allocUnsafe(64 * 1024); // 64K Chunk
fs.read(fd, chunkBuffer, 0, chunkBuffer.length, currentPosition, (err, bytesRead) => {
currentPosition += bytesRead;
if (err) {
finish(err);
} else {
if (bytesRead === 0) {
// no more data -> finish
finish();
} else {
stream.fire(chunkBuffer.slice(0, bytesRead));
readChunk();
}
}
});
};
// start reading
readChunk();
});
return stream.event;
}
export interface IDownloadServiceChannel extends IChannel {
listen(event: 'upload', uri: URI): Event<UploadResponse>;
listen(event: string, arg?: any): Event<any>;
}
export class DownloadServiceChannel implements IDownloadServiceChannel {
constructor() { }
listen(event: string, arg?: any): Event<any> {
switch (event) {
case 'upload': return buffer(upload(arg));
}
return undefined;
}
call(command: string, arg?: any): TPromise<any> {
throw new Error('No calls');
}
}
export class DownloadServiceChannelClient implements IDownloadService {
_serviceBrand: any;
constructor(private channel: IDownloadServiceChannel) { }
download(from: URI, to: string): TPromise<void> {
const dirName = path.dirname(to);
let out: fs.WriteStream;
return new TPromise((c, e) => {
return mkdirp(dirName)
.then(() => {
out = fs.createWriteStream(to);
out.once('close', () => c(null));
out.once('error', e);
const uploadStream = this.channel.listen('upload', from);
const disposable = uploadStream((result: Buffer | Error | undefined) => {
if (result === void 0) {
out.end();
out.close();
disposable.dispose();
c(null);
} else if (result instanceof Buffer) {
out.write(result);
} else if (result instanceof Error) {
out.close();
disposable.dispose();
e(result);
}
});
});
});
}
}
\ No newline at end of file
......@@ -306,7 +306,9 @@ export interface IExtensionManagementService {
onUninstallExtension: Event<IExtensionIdentifier>;
onDidUninstallExtension: Event<DidUninstallExtensionEvent>;
install(zipPath: string): TPromise<void>;
zip(extension: ILocalExtension): TPromise<URI>;
unzip(zipLocation: URI): TPromise<void>;
install(vsix: URI): TPromise<void>;
installFromGallery(extension: IGalleryExtension): TPromise<void>;
uninstall(extension: ILocalExtension, force?: boolean): TPromise<void>;
reinstallFromGallery(extension: ILocalExtension): TPromise<void>;
......
......@@ -17,7 +17,10 @@ export interface IExtensionManagementChannel extends IChannel {
listen(event: 'onDidInstallExtension'): Event<DidInstallExtensionEvent>;
listen(event: 'onUninstallExtension'): Event<IExtensionIdentifier>;
listen(event: 'onDidUninstallExtension'): Event<DidUninstallExtensionEvent>;
call(command: 'install', args: [string]): TPromise<void>;
call(command: 'zip', args: [ILocalExtension]): TPromise<URI>;
call(command: 'unzip', args: [URI]): TPromise<void>;
call(command: 'install', args: [URI]): TPromise<void>;
call(command: 'installFromGallery', args: [IGalleryExtension]): TPromise<void>;
call(command: 'uninstall', args: [ILocalExtension, boolean]): TPromise<void>;
call(command: 'reinstallFromGallery', args: [ILocalExtension]): TPromise<void>;
......@@ -53,7 +56,9 @@ export class ExtensionManagementChannel implements IExtensionManagementChannel {
call(command: string, args?: any): TPromise<any> {
switch (command) {
case 'install': return this.service.install(args[0]);
case 'zip': return this.service.zip(this._transform(args[0]));
case 'unzip': return this.service.unzip(URI.revive(args[0]));
case 'install': return this.service.install(URI.revive(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]));
......@@ -81,8 +86,16 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
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]);
zip(extension: ILocalExtension): TPromise<URI> {
return this.channel.call('zip', [extension]).then(result => URI.revive(this.uriTransformer.transformIncoming(result)));
}
unzip(zipLocation: URI): TPromise<void> {
return this.channel.call('unzip', [zipLocation]);
}
install(vsix: URI): TPromise<void> {
return this.channel.call('install', [vsix]);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
......
......@@ -5,7 +5,7 @@
'use strict';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionIdentifier, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILocalExtension, IGalleryExtension, EXTENSION_IDENTIFIER_REGEX, IExtensionIdentifier, IReportedExtension, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
if (a.uuid && b.uuid) {
......@@ -117,4 +117,28 @@ export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set<str
}
return result;
}
export function isWorkspaceExtension(manifest: IExtensionManifest): boolean {
if (manifest.main) {
const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name);
return [
'vscode.extension-editing',
'vscode.configuration-editing',
'vscode.search-rg',
'vscode.css-language-features',
'vscode.git',
'vscode.grunt',
'vscode.gulp',
'vscode.html-language-features',
'vscode.json-language-features',
'vscode.markdown-language-features',
'vscode.npm',
'vscode.php-language-features',
'vscode.typescript-language-features',
'ms-python.python',
'eg2.tslint'
].indexOf(extensionId) !== -1;
}
return false;
}
\ No newline at end of file
......@@ -10,6 +10,12 @@ import {
IExtensionManagementServerService, IExtensionManagementServer
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { flatten } from 'vs/base/common/arrays';
import { isWorkspaceExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import URI from 'vs/base/common/uri';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { Action } from 'vs/base/common/actions';
export class MulitExtensionManagementService implements IExtensionManagementService {
......@@ -23,13 +29,16 @@ export class MulitExtensionManagementService implements IExtensionManagementServ
private readonly servers: IExtensionManagementServer[];
constructor(
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService
@IExtensionManagementServerService private extensionManagementServerService: IExtensionManagementServerService,
@INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService
) {
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;
this.syncExtensions();
}
getInstalled(type?: LocalExtensionType): TPromise<ILocalExtension[]> {
......@@ -49,8 +58,16 @@ export class MulitExtensionManagementService implements IExtensionManagementServ
return this.getServer(extension).extensionManagementService.updateMetadata(extension, metadata);
}
install(zipPath: string): TPromise<void> {
return this.extensionManagementServerService.getLocalExtensionManagementServer().extensionManagementService.install(zipPath);
zip(extension: ILocalExtension): TPromise<URI> {
throw new Error('Not Supported');
}
unzip(zipLocation: URI): TPromise<void> {
return TPromise.join(this.servers.map(({ extensionManagementService }) => extensionManagementService.unzip(zipLocation))).then(() => null);
}
install(vsix: URI): TPromise<void> {
return TPromise.join(this.servers.map(({ extensionManagementService }) => extensionManagementService.install(vsix))).then(() => null);
}
installFromGallery(extension: IGalleryExtension): TPromise<void> {
......@@ -64,4 +81,65 @@ export class MulitExtensionManagementService implements IExtensionManagementServ
private getServer(extension: ILocalExtension): IExtensionManagementServer {
return this.extensionManagementServerService.getExtensionManagementServer(extension.location);
}
private async syncExtensions(): Promise<void> {
const localServer = this.extensionManagementServerService.getLocalExtensionManagementServer();
localServer.extensionManagementService.getInstalled()
.then(async localExtensions => {
const workspaceExtensions = localExtensions.filter(e => isWorkspaceExtension(e.manifest));
const otherServers = this.servers.filter(s => s !== localServer);
const extensionsToSync: Map<IExtensionManagementServer, ILocalExtension[]> = await this.getExtensionsToSync(workspaceExtensions, otherServers);
if (extensionsToSync.size > 0) {
const handler = this.notificationService.notify({ severity: Severity.Info, message: localize('synchronising', "Synchronizing workspace extensions...") });
handler.progress.infinite();
const promises: TPromise<any>[] = [];
const vsixById: Map<string, TPromise<URI>> = new Map<string, TPromise<URI>>();
extensionsToSync.forEach((extensions, server) => {
for (const extension of extensions) {
let vsix = vsixById.get(extension.galleryIdentifier.id);
if (!vsix) {
vsix = localServer.extensionManagementService.zip(extension);
vsixById.set(extension.galleryIdentifier.id, vsix);
promises.push(vsix);
}
promises.push(vsix.then(location => server.extensionManagementService.unzip(location)));
}
});
TPromise.join(promises).then(() => {
handler.progress.done();
handler.updateMessage(localize('Synchronize.finished', "Finished synchronizing workspace extensions. Please reload now."));
handler.updateActions({
primary: [
new Action('Synchronize.reloadNow', localize('Synchronize.reloadNow', "Reload Now"), null, true, () => this.windowService.reloadWindow())
]
});
});
}
}, err => {
console.log(err);
});
}
private async getExtensionsToSync(workspaceExtensions: ILocalExtension[], servers: IExtensionManagementServer[]): Promise<Map<IExtensionManagementServer, ILocalExtension[]>> {
const extensionsToSync: Map<IExtensionManagementServer, ILocalExtension[]> = new Map<IExtensionManagementServer, ILocalExtension[]>();
for (const server of servers) {
const extensions = await server.extensionManagementService.getInstalled();
const groupedById = this.groupById(extensions);
const toSync = workspaceExtensions.filter(e => !groupedById.has(e.galleryIdentifier.id));
if (toSync.length) {
extensionsToSync.set(server, toSync);
}
}
return extensionsToSync;
}
private groupById(extensions: ILocalExtension[]): Map<string, ILocalExtension> {
const result: Map<string, ILocalExtension> = new Map<string, ILocalExtension>();
for (const extension of extensions) {
result.set(extension.galleryIdentifier.id, extension);
}
return result;
}
}
\ No newline at end of file
......@@ -7,12 +7,13 @@
import * as nls from 'vs/nls';
import * as path from 'path';
import * as glob from 'glob';
import * as pfs from 'vs/base/node/pfs';
import * as errors from 'vs/base/common/errors';
import { assign } from 'vs/base/common/objects';
import { toDisposable, Disposable } from 'vs/base/common/lifecycle';
import { flatten } from 'vs/base/common/arrays';
import { extract, buffer, ExtractError } from 'vs/base/node/zip';
import { extract, buffer, ExtractError, zip, IFile } from 'vs/base/node/zip';
import { TPromise, ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension,
......@@ -41,6 +42,9 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { tmpdir } from 'os';
import { generateUuid } from 'vs/base/common/uuid';
import { IDownloadService } from 'vs/platform/download/common/download';
const SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions'));
const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem';
......@@ -136,6 +140,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
@IDialogService private dialogService: IDialogService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@ILogService private logService: ILogService,
@IDownloadService private downloadService: IDownloadService,
@ITelemetryService private telemetryService: ITelemetryService,
) {
super();
......@@ -153,8 +158,32 @@ export class ExtensionManagementService extends Disposable implements IExtension
}));
}
install(zipPath: string): TPromise<void> {
zipPath = path.resolve(zipPath);
zip(extension: ILocalExtension): TPromise<URI> {
return this.collectFiles(extension)
.then(files => zip(path.join(tmpdir(), generateUuid()), files))
.then(path => URI.file(path));
}
unzip(zipLocation: URI): TPromise<void> {
const downloadedLocation = path.join(tmpdir(), generateUuid());
return this.downloadService.download(zipLocation, downloadedLocation).then(() => this.install(URI.file(downloadedLocation)));
}
private collectFiles(extension: ILocalExtension): TPromise<IFile[]> {
return new TPromise((c, e) => {
glob('**', { cwd: extension.location.fsPath, nodir: true, dot: true }, (err: Error, files: string[]) => {
if (err) {
e(err);
} else {
c(files.map(f => f.replace(/\\/g, '/'))
.map(f => (<IFile>{ path: `extension/${f}`, localPath: path.join(extension.location.fsPath, f) })));
}
});
});
}
install(vsix: URI): TPromise<void> {
const zipPath = path.resolve(vsix.fsPath);
return validateLocalExtension(zipPath)
.then(manifest => {
......
......@@ -96,6 +96,7 @@ import { OpenerService } from 'vs/editor/browser/services/openerService';
import { SearchHistoryService } from 'vs/workbench/services/search/node/searchHistoryService';
import { MulitExtensionManagementService } from 'vs/platform/extensionManagement/common/multiExtensionManagement';
import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService';
import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc';
/**
* Services that we require for the Shell
......@@ -335,7 +336,10 @@ export class WorkbenchShell extends Disposable {
.then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${this.configuration.windowId}`));
sharedProcess
.done(client => client.registerChannel('dialog', instantiationService.createInstance(DialogChannel)));
.done(client => {
client.registerChannel('download', instantiationService.createInstance(DownloadServiceChannel));
client.registerChannel('dialog', instantiationService.createInstance(DialogChannel));
});
// Warm up font cache information before building up too many dom elements
restoreFontInfo(this.storageService);
......
......@@ -672,7 +672,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService,
location: ProgressLocation.Extensions,
title: nls.localize('installingVSIXExtension', 'Installing extension from VSIX...'),
source: `${extension}`
}, () => this.extensionService.install(extension).then(() => null));
}, () => this.extensionService.install(URI.file(extension)).then(() => null));
}
if (!(extension instanceof Extension)) {
......
......@@ -8241,7 +8241,7 @@ yauzl@^2.2.1, yauzl@^2.3.1, yauzl@^2.9.1:
buffer-crc32 "~0.2.3"
fd-slicer "~1.0.1"
yazl@^2.2.1, yazl@^2.2.2:
yazl@^2.2.1, yazl@^2.2.2, yazl@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071"
dependencies:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册