downloading the SQL tools service if does not exist (#70)
* downloading the SQL tools service if does not exist before loading the extension
This commit is contained in:
Родитель
de2c4d5973
Коммит
d6593bd1eb
|
@ -8,4 +8,5 @@ packages
|
|||
*.nupkg
|
||||
*.vsix
|
||||
tools
|
||||
examples
|
||||
examples
|
||||
sqltoolsservice
|
||||
|
|
13
gulpfile.js
13
gulpfile.js
|
@ -1,4 +1,5 @@
|
|||
var gulp = require('gulp');
|
||||
var rename = require('gulp-rename');
|
||||
var install = require('gulp-install');
|
||||
var tslint = require('gulp-tslint');
|
||||
var ts = require('gulp-typescript');
|
||||
|
@ -156,10 +157,12 @@ gulp.task('ext:copy-tests', () => {
|
|||
.pipe(gulp.dest(config.paths.project.root + '/out/test/resources/'))
|
||||
});
|
||||
|
||||
gulp.task('ext:copy-packages', () => {
|
||||
var serviceHostVersion = "0.0.9";
|
||||
return gulp.src(config.paths.project.root + '/packages/Microsoft.SqlTools.ServiceLayer.' + serviceHostVersion + '/lib/netcoreapp1.0/**/*')
|
||||
.pipe(gulp.dest(config.paths.project.root + '/out/tools/'))
|
||||
gulp.task('ext:copy-config', () => {
|
||||
var env = process.env.VsMsSqlEnv;
|
||||
env = env == undefined ? "dev" : env;
|
||||
return gulp.src(config.paths.project.root + '/src/configurations/' + env + '.config.json')
|
||||
.pipe(rename('config.json'))
|
||||
.pipe(gulp.dest(config.paths.project.root + '/out/src'));
|
||||
});
|
||||
|
||||
gulp.task('ext:copy-js', () => {
|
||||
|
@ -169,7 +172,7 @@ gulp.task('ext:copy-js', () => {
|
|||
.pipe(gulp.dest(config.paths.project.root + '/out/src'))
|
||||
});
|
||||
|
||||
gulp.task('ext:copy', gulp.series('ext:copy-tests', 'ext:copy-packages', 'ext:copy-js'));
|
||||
gulp.task('ext:copy', gulp.series('ext:copy-tests', 'ext:copy-js', 'ext:copy-config'));
|
||||
|
||||
gulp.task('ext:build', gulp.series('ext:nuget-download', 'ext:nuget-restore', 'ext:lint', 'ext:compile', 'ext:copy'));
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-tslint": "^6.0.2",
|
||||
"gulp-typescript": "^2.13.6",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"pm-mocha-jenkins-reporter": "^0.2.6",
|
||||
"tslint": "^3.14.0",
|
||||
"typescript": "^1.8.9",
|
||||
|
@ -63,7 +64,12 @@
|
|||
"request": "^2.73.0",
|
||||
"underscore": "^1.8.3",
|
||||
"vscode-extension-telemetry": "^0.0.5",
|
||||
"vscode-languageclient": "^2.0.0"
|
||||
"vscode-languageclient": "^2.0.0",
|
||||
"fs-extra-promise": "^0.3.1",
|
||||
"http-proxy-agent": "^1.0.0",
|
||||
"https-proxy-agent": "^1.0.0",
|
||||
"tmp": "^0.0.28",
|
||||
"decompress": "^4.0.0"
|
||||
},
|
||||
"contributes": {
|
||||
"languages": [
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
import * as path from 'path';
|
||||
import Utils = require('../models/utils');
|
||||
|
||||
export default class Config {
|
||||
private static _configJsonContent = undefined;
|
||||
|
||||
public static get configJsonContent(): any {
|
||||
if (this._configJsonContent === undefined) {
|
||||
this._configJsonContent = this.loadConfig();
|
||||
}
|
||||
return this._configJsonContent;
|
||||
}
|
||||
|
||||
public getSqlToolsServiceDownloadUrl(): string {
|
||||
try {
|
||||
let json = Config.configJsonContent;
|
||||
return json.sqlToolsService.downloadUrl + '/' + json.sqlToolsService.version;
|
||||
} catch (error) {
|
||||
Utils.showErrorMsg(error);
|
||||
throw(error);
|
||||
}
|
||||
}
|
||||
|
||||
public getSqlToolsInstallDirectory(): string {
|
||||
try {
|
||||
let json = Config.configJsonContent;
|
||||
return json.sqlToolsService.installDir;
|
||||
} catch (error) {
|
||||
Utils.showErrorMsg(error);
|
||||
throw(error);
|
||||
}
|
||||
}
|
||||
|
||||
public getSqlToolsExecutableFiles(): string[] {
|
||||
try {
|
||||
let json = Config.configJsonContent;
|
||||
return json.sqlToolsService.executableFiles;
|
||||
} catch (error) {
|
||||
Utils.showErrorMsg(error);
|
||||
throw(error);
|
||||
}
|
||||
}
|
||||
|
||||
static loadConfig(): any {
|
||||
let configContent = fs.readFileSync(path.join(__dirname, '../config.json'));
|
||||
return JSON.parse(configContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"sqlToolsService": {
|
||||
"downloadUrl": "http://dtnuget:8080/download/microsoft.sqltools.servicelayer",
|
||||
"version": "0.0.10",
|
||||
"installDir": "../../../sqltoolsservice",
|
||||
"executableFiles": ["Microsoft.SqlTools.ServiceLayer.exe", "Microsoft.SqlTools.ServiceLayer", "Microsoft.SqlTools.ServiceLayer.dll"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"sqlToolsService": {
|
||||
"downloadUrl": "",
|
||||
"version": "0.0.8"
|
||||
}
|
||||
}
|
|
@ -58,7 +58,9 @@ export default class ConnectionManager {
|
|||
this.vscodeWrapper.onDidCloseTextDocument(params => this.onDidCloseTextDocument(params));
|
||||
this.vscodeWrapper.onDidSaveTextDocument(params => this.onDidSaveTextDocument(params));
|
||||
|
||||
this.client.onNotification(ConnectionContracts.ConnectionChangedNotification.type, this.handleConnectionChangedNotification());
|
||||
if (this.client !== undefined) {
|
||||
this.client.onNotification(ConnectionContracts.ConnectionChangedNotification.type, this.handleConnectionChangedNotification());
|
||||
}
|
||||
}
|
||||
|
||||
private get vscodeWrapper(): VscodeWrapper {
|
||||
|
|
|
@ -21,6 +21,7 @@ export default class MainController implements vscode.Disposable {
|
|||
private _connectionMgr: ConnectionManager;
|
||||
private _prompter: IPrompter;
|
||||
private _vscodeWrapper: VscodeWrapper;
|
||||
private _initialized: boolean = false;
|
||||
|
||||
constructor(context: vscode.ExtensionContext,
|
||||
connectionManager?: ConnectionManager,
|
||||
|
@ -51,7 +52,7 @@ export default class MainController implements vscode.Disposable {
|
|||
this._statusview.dispose();
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
public activate(): Promise<boolean> {
|
||||
const self = this;
|
||||
|
||||
let activationTimer = new Utils.Timer();
|
||||
|
@ -72,33 +73,46 @@ export default class MainController implements vscode.Disposable {
|
|||
|
||||
this._vscodeWrapper = new VscodeWrapper();
|
||||
|
||||
// initialize language service client
|
||||
SqlToolsServerClient.instance.initialize(this._context);
|
||||
|
||||
// Init status bar
|
||||
this._statusview = new StatusView();
|
||||
|
||||
// Init CodeAdapter for use when user response to questions is needed
|
||||
this._prompter = new CodeAdapter();
|
||||
|
||||
// Init content provider for results pane
|
||||
this._outputContentProvider = new SqlOutputContentProvider(self._context, self._statusview);
|
||||
let registration = vscode.workspace.registerTextDocumentContentProvider(SqlOutputContentProvider.providerName, self._outputContentProvider);
|
||||
this._context.subscriptions.push(registration);
|
||||
|
||||
// Init connection manager and connection MRU
|
||||
this._connectionMgr = new ConnectionManager(self._context, self._statusview, self._prompter);
|
||||
|
||||
activationTimer.end();
|
||||
|
||||
// telemetry for activation
|
||||
Telemetry.sendTelemetryEvent(this._context, 'ExtensionActivated', {},
|
||||
{ activationTime: activationTimer.getDuration() }
|
||||
);
|
||||
|
||||
Utils.logDebug(Constants.extensionActivated);
|
||||
return this.initialize(activationTimer);
|
||||
}
|
||||
|
||||
public isInitialized(): boolean {
|
||||
return this._initialized;
|
||||
}
|
||||
|
||||
public initialize(activationTimer: Utils.Timer): Promise<boolean> {
|
||||
// initialize language service client
|
||||
return new Promise<boolean>( (resolve, reject) => {
|
||||
SqlToolsServerClient.instance.initialize(this._context).then(() => {
|
||||
const self = this;
|
||||
// Init status bar
|
||||
this._statusview = new StatusView();
|
||||
|
||||
// Init CodeAdapter for use when user response to questions is needed
|
||||
this._prompter = new CodeAdapter();
|
||||
|
||||
// Init content provider for results pane
|
||||
this._outputContentProvider = new SqlOutputContentProvider(self._context, self._statusview);
|
||||
let registration = vscode.workspace.registerTextDocumentContentProvider(SqlOutputContentProvider.providerName, self._outputContentProvider);
|
||||
this._context.subscriptions.push(registration);
|
||||
|
||||
// Init connection manager and connection MRU
|
||||
this._connectionMgr = new ConnectionManager(self._context, self._statusview, self._prompter);
|
||||
|
||||
activationTimer.end();
|
||||
|
||||
// telemetry for activation
|
||||
Telemetry.sendTelemetryEvent(this._context, 'ExtensionActivated', {},
|
||||
{ activationTime: activationTimer.getDuration() }
|
||||
);
|
||||
|
||||
Utils.logDebug(Constants.extensionActivated);
|
||||
this._initialized = true;
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Choose a new database from the current server
|
||||
private onChooseDatabase(): Promise<boolean> {
|
||||
return this._connectionMgr.onChooseDatabase();
|
||||
|
|
|
@ -6,10 +6,10 @@ let controller: MainController = undefined;
|
|||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
export function activate(context: vscode.ExtensionContext): Promise<boolean> {
|
||||
controller = new MainController(context);
|
||||
context.subscriptions.push(controller);
|
||||
controller.activate();
|
||||
return controller.activate();
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import * as stream from 'stream';
|
||||
import {parse} from 'url';
|
||||
import {Platform} from '../models/platform';
|
||||
import {getProxyAgent} from './proxy';
|
||||
import Utils = require('../models/utils');
|
||||
import Config from '../configurations/config';
|
||||
import * as path from 'path';
|
||||
import { workspace } from 'vscode';
|
||||
|
||||
let tmp = require('tmp');
|
||||
let fs = require('fs');
|
||||
const decompress = require('decompress');
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
|
||||
/*
|
||||
* Service Download Provider class which handles downloading the SQL Tools service.
|
||||
*/
|
||||
export default class ServiceDownloadProvider {
|
||||
|
||||
constructor(private _config: Config) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the download url for given platfotm
|
||||
*/
|
||||
public getDownloadFileName(platform: Platform): string {
|
||||
let fileName = 'microsoft.sqltools.servicelayer-';
|
||||
|
||||
switch (platform) {
|
||||
case Platform.Windows:
|
||||
fileName += 'win-x64-netcoreapp1.0.zip';
|
||||
break;
|
||||
case Platform.OSX:
|
||||
fileName += 'osx-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.CentOS:
|
||||
fileName += 'centos-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.Debian:
|
||||
fileName += 'debian-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.Fedora:
|
||||
fileName += 'fedora-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.OpenSUSE:
|
||||
fileName += 'opensuse-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.RHEL:
|
||||
fileName += 'rhel-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.Ubuntu14:
|
||||
fileName += 'ubuntu14-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
case Platform.Ubuntu16:
|
||||
fileName += 'ubuntu16-x64-netcoreapp1.0.tar.gz';
|
||||
break;
|
||||
default:
|
||||
if (process.platform === 'linux') {
|
||||
throw new Error('Unsupported linux distribution');
|
||||
} else {
|
||||
throw new Error('Unsupported platform: ${process.platform}');
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private download(urlString: string, proxy?: string, strictSSL?: boolean): Promise<stream.Readable> {
|
||||
process.on('uncaughtException', function (err): void {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
let url = parse(urlString);
|
||||
|
||||
const agent = getProxyAgent(url, proxy, strictSSL);
|
||||
|
||||
let client = url.protocol === 'http:' ? http : https;
|
||||
let options: http.RequestOptions = {
|
||||
host: url.hostname,
|
||||
path: url.path,
|
||||
agent: agent,
|
||||
port: +url.port
|
||||
};
|
||||
|
||||
if (url.protocol === 'https:') {
|
||||
let httpsOptions: https.RequestOptions = {
|
||||
host: url.hostname,
|
||||
path: url.path,
|
||||
agent: agent,
|
||||
port: +url.port
|
||||
};
|
||||
options = httpsOptions;
|
||||
}
|
||||
|
||||
return new Promise<stream.Readable>((resolve, reject) => {
|
||||
return client.get(options, res => {
|
||||
// handle redirection
|
||||
if (res.statusCode === 302) {
|
||||
return this.download(res.headers.location);
|
||||
} else if (res.statusCode !== 200) {
|
||||
return reject(Error('Download failed with code ${res.statusCode}.'));
|
||||
}
|
||||
|
||||
return resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SQL tools service installed folder.
|
||||
*/
|
||||
public getInstallDirectory(): string {
|
||||
let installDirFromConfig = this._config.getSqlToolsInstallDirectory();
|
||||
const basePath = path.join(__dirname, installDirFromConfig);
|
||||
if (!fs.existsSync(basePath)) {
|
||||
fs.mkdirSync(basePath);
|
||||
}
|
||||
return basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the SQL tools service and decompress it in the install folder.
|
||||
*/
|
||||
public go(platform: Platform): Promise<boolean> {
|
||||
const config = workspace.getConfiguration();
|
||||
const proxy = config.get<string>('http.proxy');
|
||||
const strictSSL = config.get('http.proxyStrictSSL', true);
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const fileName = this.getDownloadFileName( platform);
|
||||
const installDirectory = this.getInstallDirectory();
|
||||
|
||||
Utils.logDebug('Installing sql tools service to ${installDirectory}');
|
||||
let baseDownloadUrl = this._config.getSqlToolsServiceDownloadUrl();
|
||||
const urlString = baseDownloadUrl + '/' + fileName;
|
||||
|
||||
Utils.logDebug('Attempting to download ${fileName}');
|
||||
|
||||
return this.download(urlString, proxy, strictSSL)
|
||||
.then(inStream => {
|
||||
tmp.file((err, tmpPath, fd, cleanupCallback) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
Utils.logDebug('Downloading to ${tmpPath}...');
|
||||
|
||||
const outStream = fs.createWriteStream(undefined, { fd: fd });
|
||||
|
||||
outStream.once('error', outStreamErr => reject(outStreamErr));
|
||||
inStream.once('error', inStreamErr => reject(inStreamErr));
|
||||
|
||||
outStream.once('finish', () => {
|
||||
// At this point, the asset has finished downloading.
|
||||
|
||||
Utils.logDebug('Download complete!');
|
||||
Utils.logDebug('Decompressing...');
|
||||
|
||||
return decompress(tmpPath, installDirectory)
|
||||
.then(files => {
|
||||
Utils.logDebug('Done! ${files.length} files unpacked.\n');
|
||||
return resolve(true);
|
||||
})
|
||||
.catch(decompressErr => {
|
||||
Utils.logDebug('[ERROR] ${err}');
|
||||
return reject(decompressErr);
|
||||
});
|
||||
});
|
||||
|
||||
inStream.pipe(outStream);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
Utils.logDebug('[ERROR] ${err}');
|
||||
reject(err);
|
||||
});
|
||||
}).then(res => {
|
||||
return res;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Url, parse as parseUrl } from 'url';
|
||||
let HttpProxyAgent = require('http-proxy-agent');
|
||||
let HttpsProxyAgent = require('https-proxy-agent');
|
||||
|
||||
function getSystemProxyURL(requestURL: Url): string {
|
||||
if (requestURL.protocol === 'http:') {
|
||||
return process.env.HTTP_PROXY || process.env.http_proxy || undefined;
|
||||
} else if (requestURL.protocol === 'https:') {
|
||||
return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || undefined;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the proxy agent using the proxy url in the parameters or the system proxy. Returns null if no proxy found
|
||||
*/
|
||||
export function getProxyAgent(requestURL: Url, proxy?: string, strictSSL?: boolean): any {
|
||||
const proxyURL = proxy || getSystemProxyURL(requestURL);
|
||||
|
||||
if (!proxyURL) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const proxyEndpoint = parseUrl(proxyURL);
|
||||
|
||||
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
strictSSL = strictSSL || true;
|
||||
|
||||
const opts = {
|
||||
host: proxyEndpoint.hostname,
|
||||
port: Number(proxyEndpoint.port),
|
||||
auth: proxyEndpoint.auth,
|
||||
rejectUnauthorized: strictSSL
|
||||
};
|
||||
|
||||
return requestURL.protocol === 'http:' ? new HttpProxyAgent(opts) : new HttpsProxyAgent(opts);
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as path from 'path';
|
||||
import * as Utils from '../models/utils';
|
||||
import {Platform, getCurrentPlatform} from '../models/platform';
|
||||
import ServiceDownloadProvider from './download';
|
||||
import StatusView from '../views/statusView';
|
||||
import Config from '../configurations/config';
|
||||
let fs = require('fs-extra-promise');
|
||||
|
||||
/*
|
||||
* Service Provider class finds the SQL tools service executable file or downloads it if doesn't exist.
|
||||
*/
|
||||
export default class ServerProvider {
|
||||
|
||||
constructor(private _downloadProvider?: ServiceDownloadProvider,
|
||||
private _config?: Config,
|
||||
private _statusView?: StatusView) {
|
||||
if (!this._config) {
|
||||
this._config = new Config();
|
||||
}
|
||||
if (!this._downloadProvider) {
|
||||
this._downloadProvider = new ServiceDownloadProvider(this._config);
|
||||
}
|
||||
if (!this._statusView) {
|
||||
this._statusView = new StatusView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a file path, returns the path to the SQL Tools service file.
|
||||
*/
|
||||
public findServerPath(filePath: string): Promise<string> {
|
||||
return fs.lstatAsync(filePath).then(stats => {
|
||||
// If a file path was passed, assume its the launch file.
|
||||
if (stats.isFile()) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// Otherwise, search the specified folder.
|
||||
let candidate: string;
|
||||
|
||||
if (this._config !== undefined) {
|
||||
let executableFiles: string[] = this._config.getSqlToolsExecutableFiles();
|
||||
executableFiles.forEach(element => {
|
||||
let executableFile = path.join(filePath, element);
|
||||
if (candidate === undefined && fs.existsSync(executableFile)) {
|
||||
candidate = executableFile;
|
||||
return candidate;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return candidate;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the SQL tools service if doesn't exist and returns the file path.
|
||||
*/
|
||||
public getServerPath(): Promise<string> {
|
||||
|
||||
// Attempt to find launch file path first from options, and then from the default install location.
|
||||
// If SQL tools service can't be found, download it.
|
||||
|
||||
const installDirectory = this._downloadProvider.getInstallDirectory();
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
return this.findServerPath(installDirectory).then(result => {
|
||||
if (result === undefined) {
|
||||
return this.downloadServerFiles().then ( downloadResult => {
|
||||
resolve(downloadResult);
|
||||
});
|
||||
} else {
|
||||
return resolve(result);
|
||||
}
|
||||
}).catch(err => {
|
||||
return reject(err);
|
||||
});
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
private downloadServerFiles(): Promise<string> {
|
||||
const platform = getCurrentPlatform();
|
||||
if (platform === Platform.Unknown) {
|
||||
throw new Error('Invalid Platform');
|
||||
}
|
||||
|
||||
const installDirectory = this._downloadProvider.getInstallDirectory();
|
||||
let currentFileUrl = Utils.getActiveTextEditorUri();
|
||||
this._statusView.installingService(currentFileUrl);
|
||||
return this._downloadProvider.go(platform).then( _ => {
|
||||
return this.findServerPath(installDirectory).then ( result => {
|
||||
this._statusView.serviceInstalled(currentFileUrl);
|
||||
return result;
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,13 +4,16 @@
|
|||
* ------------------------------------------------------------------------------------------ */
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions,
|
||||
TransportKind, RequestType, NotificationType, NotificationHandler } from 'vscode-languageclient';
|
||||
import * as Utils from '../models/utils';
|
||||
import {VersionRequest} from '../models/contracts';
|
||||
import Constants = require('../models/constants');
|
||||
import ServerProvider from './server';
|
||||
import ServiceDownloadProvider from './download';
|
||||
import Config from '../configurations/config';
|
||||
import StatusView from '../views/statusView';
|
||||
|
||||
// The Service Client class handles communication with the VS Code LanguageClient
|
||||
export default class SqlToolsServiceClient {
|
||||
|
@ -29,42 +32,58 @@ export default class SqlToolsServiceClient {
|
|||
this._client = client;
|
||||
}
|
||||
|
||||
constructor(private _server: ServerProvider) {
|
||||
}
|
||||
|
||||
// gets or creates the singleton SQL Tools service client instance
|
||||
public static get instance(): SqlToolsServiceClient {
|
||||
if (this._instance === undefined) {
|
||||
this._instance = new SqlToolsServiceClient();
|
||||
let config = new Config();
|
||||
let downloadProvider = new ServiceDownloadProvider(config);
|
||||
let statusView = new StatusView();
|
||||
let serviceProvider = new ServerProvider(downloadProvider, config, statusView);
|
||||
this._instance = new SqlToolsServiceClient(serviceProvider);
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
// initialize the SQL Tools Service Client instance by launching
|
||||
// out-of-proc server through the LanguageClient
|
||||
public initialize(context: ExtensionContext): void {
|
||||
public initialize(context: ExtensionContext): Promise<boolean> {
|
||||
return new Promise<boolean>( (resolve, reject) => {
|
||||
this._server.getServerPath().then(serverPath => {
|
||||
let serverArgs = [];
|
||||
let serverCommand = serverPath;
|
||||
if (serverPath.endsWith('.dll')) {
|
||||
serverArgs = [serverPath];
|
||||
serverCommand = 'dotnet';
|
||||
}
|
||||
// run the service host using dotnet.exe from the path
|
||||
let serverOptions: ServerOptions = { command: serverCommand, args: serverArgs, transport: TransportKind.stdio };
|
||||
|
||||
// run the service host using dotnet.exe from the path
|
||||
let serverCommand = 'dotnet';
|
||||
let serverArgs = [ context.asAbsolutePath(path.join('./out/tools', 'Microsoft.SqlTools.ServiceLayer.dll')) ];
|
||||
let serverOptions: ServerOptions = { command: serverCommand, args: serverArgs, transport: TransportKind.stdio };
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
documentSelector: ['sql'],
|
||||
synchronize: {
|
||||
configurationSection: 'sqlTools'
|
||||
}
|
||||
};
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: LanguageClientOptions = {
|
||||
documentSelector: ['sql'],
|
||||
synchronize: {
|
||||
configurationSection: 'sqlTools'
|
||||
}
|
||||
};
|
||||
// cache the client instance for later use
|
||||
this.client = new LanguageClient('sqlserverclient', serverOptions, clientOptions);
|
||||
this.client.onReady().then( () => {
|
||||
this.checkServiceCompatibility();
|
||||
});
|
||||
// Create the language client and start the client.
|
||||
let disposable = this.client.start();
|
||||
|
||||
// cache the client instance for later use
|
||||
this.client = new LanguageClient('sqlserverclient', serverOptions, clientOptions);
|
||||
this.client.onReady().then( () => {
|
||||
this.checkServiceCompatibility();
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
// client can be deactivated on extension deactivation
|
||||
context.subscriptions.push(disposable);
|
||||
resolve(true);
|
||||
|
||||
});
|
||||
});
|
||||
// Create the language client and start the client.
|
||||
let disposable = this.client.start();
|
||||
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
// client can be deactivated on extension deactivation
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,7 +93,9 @@ export default class SqlToolsServiceClient {
|
|||
* @returns A thenable object for when the request receives a response
|
||||
*/
|
||||
public sendRequest<P, R, E>(type: RequestType<P, R, E>, params?: P): Thenable<R> {
|
||||
return this.client.sendRequest(type, params);
|
||||
if (this.client !== undefined) {
|
||||
return this.client.sendRequest(type, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,7 +104,9 @@ export default class SqlToolsServiceClient {
|
|||
* @param handler The handler to register
|
||||
*/
|
||||
public onNotification<P>(type: NotificationType<P>, handler: NotificationHandler<P>): void {
|
||||
return this.client.onNotification(type, handler);
|
||||
if (this._client !== undefined) {
|
||||
return this.client.onNotification(type, handler);
|
||||
}
|
||||
}
|
||||
|
||||
public checkServiceCompatibility(): Promise<boolean> {
|
||||
|
|
|
@ -126,6 +126,8 @@ export const executeQueryCommandCompleted = 'Command(s) completed successfully.'
|
|||
|
||||
export const serviceCompatibleVersion = '1.0.0';
|
||||
export const serviceNotCompatibleError = 'Client is not compatiable with the service layer';
|
||||
export const serviceInstalling = 'Installing Sql Tools Service';
|
||||
export const serviceInstalled = 'Sql Tools Service installed';
|
||||
|
||||
export const untitledScheme = 'untitled';
|
||||
export const untitledSaveTimeThreshold = 10.0;
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as child_process from 'child_process';
|
||||
|
||||
export enum Platform {
|
||||
Unknown,
|
||||
Windows,
|
||||
OSX,
|
||||
CentOS,
|
||||
Debian,
|
||||
Fedora,
|
||||
OpenSUSE,
|
||||
RHEL,
|
||||
Ubuntu14,
|
||||
Ubuntu16
|
||||
}
|
||||
|
||||
export function getCurrentPlatform(): Platform {
|
||||
if (process.platform === 'win32') {
|
||||
return Platform.Windows;
|
||||
} else if (process.platform === 'darwin') {
|
||||
return Platform.OSX;
|
||||
} else if (process.platform === 'linux') {
|
||||
// Get the text of /etc/os-release to discover which Linux distribution we're running on.
|
||||
// For details: https://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
const text = child_process.execSync('cat /etc/os-release').toString();
|
||||
const lines = text.split('\n');
|
||||
|
||||
function getValue(name: string): String {
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
if (line.startsWith(name)) {
|
||||
const equalsIndex = line.indexOf('=');
|
||||
if (equalsIndex >= 0) {
|
||||
let value = line.substring(equalsIndex + 1);
|
||||
|
||||
// Strip double quotes if necessary
|
||||
if (value.length > 1 && value.startsWith('"') && value.endsWith('"')) {
|
||||
value = value.substring(1, value.length - 1);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const id = getValue('ID');
|
||||
|
||||
switch (id) {
|
||||
case 'ubuntu':
|
||||
const versionId = getValue('VERSION_ID');
|
||||
if (versionId.startsWith('14')) {
|
||||
// This also works for Linux Mint
|
||||
return Platform.Ubuntu14;
|
||||
} else if (versionId.startsWith('16')) {
|
||||
return Platform.Ubuntu16;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'centos':
|
||||
return Platform.CentOS;
|
||||
case 'fedora':
|
||||
return Platform.Fedora;
|
||||
case 'opensuse':
|
||||
return Platform.OpenSUSE;
|
||||
case 'rhel':
|
||||
return Platform.RHEL;
|
||||
case 'debian':
|
||||
return Platform.Debian;
|
||||
case 'ol':
|
||||
// Oracle Linux is binary compatible with CentOS
|
||||
return Platform.CentOS;
|
||||
case 'elementary OS':
|
||||
const eOSVersionId = getValue('VERSION_ID');
|
||||
if (eOSVersionId.startsWith('0.3')) {
|
||||
// Elementary OS 0.3 Freya is binary compatible with Ubuntu 14.04
|
||||
return Platform.Ubuntu14;
|
||||
} else if (eOSVersionId.startsWith('0.4')) {
|
||||
// Elementary OS 0.4 Loki is binary compatible with Ubuntu 16.04
|
||||
return Platform.Ubuntu16;
|
||||
}
|
||||
default:
|
||||
return Platform.Windows;
|
||||
}
|
||||
}
|
||||
|
||||
return Platform.Unknown;
|
||||
}
|
|
@ -128,6 +128,20 @@ export default class StatusView implements vscode.Disposable {
|
|||
bar.statusQuery.hide();
|
||||
}
|
||||
|
||||
public installingService(fileUri: string): void {
|
||||
let bar = this.getStatusBar(fileUri);
|
||||
bar.statusConnection.command = undefined;
|
||||
this.showStatusBarItem(fileUri, bar.statusConnection);
|
||||
this.showProgress(fileUri, Constants.serviceInstalling, bar.statusConnection);
|
||||
}
|
||||
|
||||
public serviceInstalled(fileUri: string): void {
|
||||
let bar = this.getStatusBar(fileUri);
|
||||
bar.statusConnection.command = undefined;
|
||||
bar.statusConnection.text = Constants.serviceInstalled;
|
||||
this.showStatusBarItem(fileUri, bar.statusConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a new uri with an existing Uri's status bar
|
||||
*
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import assert = require('assert');
|
||||
|
||||
import Config from '../src/configurations/config';
|
||||
import Telemetry from '../src/models/telemetry';
|
||||
|
||||
suite('Config Tests', () => {
|
||||
setup(() => {
|
||||
// Ensure that telemetry is disabled while testing
|
||||
Telemetry.disable();
|
||||
});
|
||||
|
||||
test('getSqlToolsServiceDownloadUrl should return valid value', (done) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let config = new Config();
|
||||
let serviceDownloawUrl = config.getSqlToolsServiceDownloadUrl;
|
||||
assert.notEqual(serviceDownloawUrl, undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -33,7 +33,7 @@ suite('Initialization Tests', () => {
|
|||
let controller: MainController = Extension.getController();
|
||||
let connectionManager: ConnectionManager = controller.connectionManager;
|
||||
assert.notStrictEqual(undefined, connectionManager.client);
|
||||
done();
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import assert = require('assert');
|
||||
import {Platform, getCurrentPlatform} from '../src/models/platform';
|
||||
import Telemetry from '../src/models/telemetry';
|
||||
|
||||
function getPlatform(): Promise<Platform> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let platform = getCurrentPlatform();
|
||||
resolve(platform);
|
||||
});
|
||||
}
|
||||
|
||||
suite('Platform Tests', () => {
|
||||
setup(() => {
|
||||
// Ensure that telemetry is disabled while testing
|
||||
Telemetry.disable();
|
||||
});
|
||||
|
||||
test('getCurrentPlatform should return valid value', (done) => {
|
||||
getPlatform().then(platform => {
|
||||
assert.notEqual(platform, Platform.Unknown);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
import * as TypeMoq from 'typemoq';
|
||||
import assert = require('assert');
|
||||
import ServiceDownloadProvider from '../src/languageservice/download';
|
||||
import ServerProvider from '../src/languageservice/server';
|
||||
import StatusView from './../src/views/statusView';
|
||||
import Config from './../src/configurations/config';
|
||||
import * as path from 'path';
|
||||
import {getCurrentPlatform} from '../src/models/platform';
|
||||
|
||||
suite('Server tests', () => {
|
||||
|
||||
let testDownloadProvider: TypeMoq.Mock<ServiceDownloadProvider>;
|
||||
let testStatusView: TypeMoq.Mock<StatusView>;
|
||||
let testConfig: TypeMoq.Mock<Config>;
|
||||
|
||||
setup(() => {
|
||||
testDownloadProvider = TypeMoq.Mock.ofType(ServiceDownloadProvider, TypeMoq.MockBehavior.Strict);
|
||||
testStatusView = TypeMoq.Mock.ofType(StatusView, TypeMoq.MockBehavior.Strict);
|
||||
testConfig = TypeMoq.Mock.ofType(Config, TypeMoq.MockBehavior.Strict);
|
||||
});
|
||||
|
||||
test('findServerPath should return error given a folder with no installed service', () => {
|
||||
let installDir = __dirname;
|
||||
testConfig.setup(x => x.getSqlToolsExecutableFiles()).returns(() => ['exeFile1', 'exeFile2']);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.findServerPath(installDir).then( result => {
|
||||
assert.equal(result, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('findServerPath should return error given a folder with no installed service', () => {
|
||||
let installDir = __dirname;
|
||||
testConfig.setup(x => x.getSqlToolsExecutableFiles()).returns(() => ['exeFile1', 'exeFile2']);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.findServerPath(installDir).then( result => {
|
||||
assert.equal(result, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('findServerPath should return the file path given a file that exists', () => {
|
||||
let installDir = __dirname;
|
||||
let fileName = path.join(installDir, __filename);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.findServerPath(fileName).then( result => {
|
||||
assert.equal(result, fileName);
|
||||
});
|
||||
});
|
||||
|
||||
test('findServerPath should not return the given file path if doesn not exist', () => {
|
||||
let installDir = __dirname;
|
||||
let fileName = path.join(installDir, __filename);
|
||||
testConfig.setup(x => x.getSqlToolsExecutableFiles()).returns(() => ['exeFile1', 'exeFile2']);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.findServerPath(fileName).then( result => {
|
||||
assert.equal(fileName, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('findServerPath should return a valid file path given a folder with installed service', () => {
|
||||
let installDir = __dirname;
|
||||
let fileName = __filename;
|
||||
testConfig.setup(x => x.getSqlToolsExecutableFiles()).returns(() => ['exeFile1', fileName]);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.findServerPath(fileName).then( result => {
|
||||
assert.equal(result, path.join(installDir, fileName));
|
||||
});
|
||||
});
|
||||
|
||||
test('getServerPath should download the service if not exist and return the valid service file path', () => {
|
||||
let installDir = __dirname;
|
||||
let fileName: string = __filename.replace(installDir, '');
|
||||
const platform = getCurrentPlatform();
|
||||
let executables: string[] = ['exeFile1'];
|
||||
|
||||
testConfig.setup(x => x.getSqlToolsExecutableFiles()).returns(() => executables);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
testStatusView.setup(x => x.serviceInstalled(TypeMoq.It.isAny()));
|
||||
testStatusView.setup(x => x.installingService(TypeMoq.It.isAny()));
|
||||
testDownloadProvider.setup(x => x.go(platform)).callback(() => {
|
||||
executables = [fileName];
|
||||
|
||||
}).returns(() => { return Promise.resolve(true); });
|
||||
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.getServerPath().then( result => {
|
||||
assert.equal(result, path.join(installDir, fileName));
|
||||
});
|
||||
});
|
||||
|
||||
test('getServerPath should not download the service if already exist', () => {
|
||||
let installDir = __dirname;
|
||||
let fileName: string = __filename.replace(installDir, '');
|
||||
let executables: string[] = [fileName];
|
||||
|
||||
testConfig.setup(x => x.getSqlToolsExecutableFiles()).returns(() => executables);
|
||||
testDownloadProvider.setup(x => x.getInstallDirectory()).returns(() => installDir);
|
||||
testStatusView.setup(x => x.serviceInstalled(TypeMoq.It.isAny()));
|
||||
testStatusView.setup(x => x.installingService(TypeMoq.It.isAny()));
|
||||
|
||||
let server = new ServerProvider(testDownloadProvider.object, testConfig.object, testStatusView.object);
|
||||
|
||||
server.getServerPath().then( result => {
|
||||
assert.equal(result, path.join(installDir, fileName));
|
||||
});
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче