Skip to content
This repository has been archived by the owner on Jun 20, 2018. It is now read-only.

Commit

Permalink
Add support for vscode:extension/ resolver and http(s):// resolver (#19)
Browse files Browse the repository at this point in the history
Signed-off-by: Florent BENOIT <fbenoit@redhat.com>
  • Loading branch information
benoitf committed Jun 19, 2018
1 parent 4443d59 commit f1004f1
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
*/

import { ContainerModule } from 'inversify';
import { PluginDeployerFileHandler, PluginDeployerDirectoryHandler, PluginScanner } from "@theia/plugin-ext";
import { PluginDeployerFileHandler, PluginDeployerDirectoryHandler, PluginScanner, PluginDeployerResolver } from "@theia/plugin-ext";
import { PluginVsCodeFileHandler } from "./plugin-vscode-file-handler";
import { PluginVsCodeDirectoryHandler } from "./plugin-vscode-directory-handler";
import { VsCodePluginScanner } from "./scanner-vscode";
import { VsCodePluginDeployerResolver } from './plugin-vscode-resolver';

export default new ContainerModule(bind => {
bind(PluginDeployerFileHandler).to(PluginVsCodeFileHandler).inSingletonScope();
bind(PluginDeployerDirectoryHandler).to(PluginVsCodeDirectoryHandler).inSingletonScope();
bind(PluginScanner).to(VsCodePluginScanner).inSingletonScope();
bind(PluginDeployerResolver).to(VsCodePluginDeployerResolver).inSingletonScope();
}
);
114 changes: 114 additions & 0 deletions packages/plugin-ext-vscode/src/node/plugin-vscode-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { PluginDeployerResolver, PluginDeployerResolverContext } from "@theia/plugin-ext";
import { injectable } from "inversify";
import * as request from "request";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";

/**
* Resolver that handle the vscode: protocol
*/
@injectable()
export class VsCodePluginDeployerResolver implements PluginDeployerResolver {

private static PREFIX = 'vscode:extension/';

private static MARKET_PLACE_ENDPOINT = 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery';

private static HEADERS = {
'Content-Type': 'application/json',
'Accept': 'application/json;api-version=3.0-preview.1'
};

private unpackedFolder: string;
constructor() {
this.unpackedFolder = path.resolve(os.tmpdir(), 'vscode-extension-marketplace');
if (!fs.existsSync(this.unpackedFolder)) {
fs.mkdirSync(this.unpackedFolder);
}
}

/**
* Download vscode extensions if available and add them as plugins.
*/
async resolve(pluginResolverContext: PluginDeployerResolverContext): Promise<void> {

// download the file
return new Promise<void>((resolve, reject) => {
// extract name
const extracted = /^vscode:extension\/(.*)/gm.exec(pluginResolverContext.getOriginId());

if (!extracted || extracted === null) {
reject('Invalid extension' + pluginResolverContext.getOriginId());
return;
}
const extensionName = extracted[1];
const wantedExtensionVersion = null;

const json = {
"filters": [{
"criteria": [{ "filterType": 7, "value": extensionName }], "pageNumber": 1,
"pageSize": 1, "sortBy": 0, "sortOrder": 0
}], "assetTypes": ["Microsoft.VisualStudio.Services.VSIXPackage"],
"flags": 131
};

const options = {
url: VsCodePluginDeployerResolver.MARKET_PLACE_ENDPOINT,
headers: VsCodePluginDeployerResolver.HEADERS,
method: 'POST',
json: json
};

request(options, (error, response, body) => {
if (error) {
reject(error);
} else if (response.statusCode === 200) {
const extension = body.results[0].extensions[0];
if (!extension) {
reject('No extension');
}
let asset;
if (wantedExtensionVersion !== null) {
const extensionVersion = extension.versions.filter((value: any) => value.version === '0.0.1')[0];
asset = extensionVersion.files.filter((f: any) => f.assetType === 'Microsoft.VisualStudio.Services.VSIXPackage')[0];
} else {
// take first one
asset = extension.versions[0].files.filter((f: any) => f.assetType === 'Microsoft.VisualStudio.Services.VSIXPackage')[0];
}
const shortName = pluginResolverContext.getOriginId().replace(/\W/g, '_');
const unpackedPath = path.resolve(this.unpackedFolder, path.basename(shortName + '.vsix'));
const finish = () => {
pluginResolverContext.addPlugin(pluginResolverContext.getOriginId(), unpackedPath);
resolve();
};

const dest = fs.createWriteStream(unpackedPath);
dest.addListener('finish', finish);

request.get(asset.source)
.on('error', (err) => {
reject(err);
}).pipe(dest);
} else {
reject(new Error('Invalid status code' + response.statusCode + ' and message is ' + response.statusMessage));
}
});
});

}

/**
* Handle only the plugins that starts with vscode:
*/
accept(pluginId: string): boolean {
return pluginId.startsWith(VsCodePluginDeployerResolver.PREFIX);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { LocalDirectoryPluginDeployerResolver } from "./resolvers/plugin-local-d
import { PluginTheiaFileHandler } from "./handlers/plugin-theia-file-handler";
import { PluginTheiaDirectoryHandler } from "./handlers/plugin-theia-directory-handler";
import { GithubPluginDeployerResolver } from "./plugin-github-resolver";
import { HttpPluginDeployerResolver } from "./plugin-http-resolver";

export function bindMainBackend(bind: interfaces.Bind): void {
bind(PluginApiContribution).toSelf().inSingletonScope();
Expand All @@ -26,6 +27,8 @@ export function bindMainBackend(bind: interfaces.Bind): void {

bind(PluginDeployerResolver).to(LocalDirectoryPluginDeployerResolver).inSingletonScope();
bind(PluginDeployerResolver).to(GithubPluginDeployerResolver).inSingletonScope();
bind(PluginDeployerResolver).to(HttpPluginDeployerResolver).inSingletonScope();

bind(PluginDeployerFileHandler).to(PluginTheiaFileHandler).inSingletonScope();
bind(PluginDeployerDirectoryHandler).to(PluginTheiaDirectoryHandler).inSingletonScope();
}
82 changes: 82 additions & 0 deletions packages/plugin-ext/src/main/node/plugin-http-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { injectable } from "inversify";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as url from "url";
import * as request from "request";

import { PluginDeployerResolver, PluginDeployerResolverContext } from "../../common";

/**
* Resolver that handle the http(s): protocol
* http://path/to/my.plugin
* https://path/to/my.plugin
*/
@injectable()
export class HttpPluginDeployerResolver implements PluginDeployerResolver {

private unpackedFolder: string;

constructor() {
this.unpackedFolder = path.resolve(os.tmpdir(), 'http-remote');
if (!fs.existsSync(this.unpackedFolder)) {
fs.mkdirSync(this.unpackedFolder);
}
}

/**
* Grab the remote file specified by the given URL
*/
async resolve(pluginResolverContext: PluginDeployerResolverContext): Promise<void> {

// download the file
return new Promise<void>((resolve, reject) => {

// keep filename of the url
const urlPath = pluginResolverContext.getOriginId();
const link = url.parse(urlPath);
if (!link.pathname) {
reject('invalid link URI' + urlPath);
return;
}

const dirname = path.dirname(link.pathname);
const basename = path.basename(link.pathname);
const filename = dirname.replace(/\W/g, '_') + ('-') + basename;
const unpackedPath = path.resolve(this.unpackedFolder, path.basename(filename));

const finish = () => {
pluginResolverContext.addPlugin(pluginResolverContext.getOriginId(), unpackedPath);
resolve();
};

// use of cache. If file is already there use it directly
if (fs.existsSync(unpackedPath)) {
finish();
return;
}
const dest = fs.createWriteStream(unpackedPath);

dest.addListener('finish', finish);
request.get(pluginResolverContext.getOriginId())
.on('error', (err) => {
reject(err);
}).pipe(dest);
});

}

/**
* Handle only the plugins that starts with http or https:
*/
accept(pluginId: string): boolean {
return /^http[s]?:\/\/.*$/gm.test(pluginId);
}
}

0 comments on commit f1004f1

Please sign in to comment.