mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Refactor out extensions-modules and rewrite mssql extension (#909)
* commting .d.ts changes * added serverinfo to .d.ts * maybe its working? * works * updated contrib * remove unnecessary code * fix compile errors * init * conitnue * on the way to working maybe? * close * EVERYTHING WORKS * moved src out of client folder * formatting * reenable logging * working on build file * fixed install service gulp command * fix the command to properly return promises * clean up code * add telemetry * formatting * added logging/telemetry/statusview * formatting * address comments * resolute vscode-language versions
This commit is contained in:
@@ -13,14 +13,6 @@ const filter = require('gulp-filter');
|
||||
|
||||
gulp.task('clean-mssql-extension', util.rimraf('extensions/mssql/node_modules'));
|
||||
gulp.task('clean-credentials-extension', util.rimraf('extensions/credentials/node_modules'));
|
||||
gulp.task('clean-extensions-modules', util.rimraf('extensions-modules/node_modules'));
|
||||
gulp.task('clean-protocol', ['clean-extensions-modules', 'clean-mssql-extension', 'clean-credentials-extension', 'clean-client', 'clean-jsonrpc', 'clean-server', 'clean-types']);
|
||||
|
||||
// Tasks to clean extensions modules
|
||||
gulp.task('clean-mssql-ext-mod', util.rimraf('extensions/mssql/node_modules/extensions-modules'));
|
||||
gulp.task('clean-credentials-ext-mod', util.rimraf('extensions/credentials/node_modules/extensions-modules'));
|
||||
gulp.task('clean-build-ext-mod', util.rimraf('build/node_modules/extensions-modules'));
|
||||
gulp.task('clean-ext-mod', ['clean-mssql-ext-mod', 'clean-credentials-ext-mod', 'clean-build-ext-mod', 'clean-extensions-modules']);
|
||||
|
||||
gulp.task('fmt', () => formatStagedFiles());
|
||||
const formatFiles = (some) => {
|
||||
|
||||
@@ -31,7 +31,8 @@ const packageJson = require('../package.json');
|
||||
const product = require('../product.json');
|
||||
const crypto = require('crypto');
|
||||
const i18n = require('./lib/i18n');
|
||||
const serviceInstaller = require('../extensions-modules/lib/languageservice/serviceInstallerUtil');
|
||||
const serviceDownloader = require('service-downloader').ServiceDownloadProvider;
|
||||
const platformInfo = require('service-downloader/out/platform').PlatformInformation;
|
||||
const glob = require('glob');
|
||||
const deps = require('./dependencies');
|
||||
const getElectronVersion = require('./lib/electron').getElectronVersion;
|
||||
@@ -325,8 +326,6 @@ function packageTask(platform, arch, opts) {
|
||||
const localExtensionDependencies = gulp.src(extensionDepsSrc, { base: '.', dot: true })
|
||||
.pipe(filter(['**', '!**/package-lock.json']))
|
||||
.pipe(util.cleanNodeModule('account-provider-azure', ['node_modules/date-utils/doc/**', 'node_modules/adal_node/node_modules/**'], undefined))
|
||||
.pipe(util.cleanNodeModule('dataprotocol-client', ['node_modules/**', 'src/*.js'], undefined))
|
||||
.pipe(util.cleanNodeModule('extensions-modules', ['node_modules/**', 'src/*.js'], undefined))
|
||||
.pipe(util.cleanNodeModule('typescript', ['**/**'], undefined));
|
||||
|
||||
const sources = es.merge(src, localExtensions, localExtensionDependencies)
|
||||
@@ -674,27 +673,24 @@ gulp.task('generate-vscode-configuration', () => {
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Install service locally before building carbon
|
||||
|
||||
function installService(extObj, path) {
|
||||
var installer = new serviceInstaller.ServiceInstaller(extObj, path);
|
||||
installer.getServiceInstallDirectoryRoot().then(serviceInstallFolder => {
|
||||
console.log('Cleaning up the install folder: ' + serviceInstallFolder);
|
||||
del(serviceInstallFolder + '/*').then(() => {
|
||||
console.log('Installing the service. Install folder: ' + serviceInstallFolder);
|
||||
installer.installService();
|
||||
}, delError => {
|
||||
console.log('failed to delete the install folder error: ' + delError);
|
||||
});
|
||||
}, getFolderPathError => {
|
||||
console.log('failed to call getServiceInstallDirectoryRoot error: ' + getFolderPathError);
|
||||
function installService() {
|
||||
let config = require('../extensions/mssql/src/config.json');
|
||||
return platformInfo.getCurrent().then(p => {
|
||||
let runtime = p.runtimeId;
|
||||
// fix path since it won't be correct
|
||||
config.installDirectory = path.join(__dirname, '../extensions/mssql/src', config.installDirectory);
|
||||
var installer = new serviceDownloader(config);
|
||||
let serviceInstallFolder = installer.getInstallDirectory(runtime);
|
||||
console.log('Cleaning up the install folder: ' + serviceInstallFolder);
|
||||
return del(serviceInstallFolder + '/*').then(() => {
|
||||
console.log('Installing the service. Install folder: ' + serviceInstallFolder);
|
||||
return installer.installService(runtime);
|
||||
}, delError => {
|
||||
console.log('failed to delete the install folder error: ' + delError);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
gulp.task('install-sqltoolsservice', () => {
|
||||
var mssqlExt = require('../extensions/mssql/client/out/models/constants');
|
||||
var extObj = new mssqlExt.Constants();
|
||||
var path = '../extensions/mssql/client/out/config.json';
|
||||
return installService(extObj, path);
|
||||
return installService();
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ function yarnInstall(location, opts) {
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
yarnInstall('extensions-modules');
|
||||
yarnInstall('extensions'); // node modules shared by all extensions
|
||||
|
||||
const extensions = [
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
"azure-storage": "^2.1.0",
|
||||
"decompress": "^4.2.0",
|
||||
"documentdb": "1.13.0",
|
||||
"extensions-modules": "file:../extensions-modules",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"fs-extra-promise": "^1.0.1",
|
||||
"mime": "^1.3.4",
|
||||
"minimist": "^1.2.0",
|
||||
"typescript": "2.6.1",
|
||||
"vscode": "^1.0.1",
|
||||
"vscode": "^1.0.1",
|
||||
"xml2js": "^0.4.17"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
4291
build/yarn.lock
4291
build/yarn.lock
File diff suppressed because it is too large
Load Diff
3
extensions-modules/.gitignore
vendored
3
extensions-modules/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
lib
|
||||
*.log
|
||||
@@ -1,9 +0,0 @@
|
||||
.vscode/
|
||||
lib/test/
|
||||
lib/**/*.map
|
||||
src/
|
||||
test/
|
||||
.eslintrc
|
||||
.gitignore
|
||||
gulpfile.js
|
||||
tsd.json
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "extensions-modules",
|
||||
"version": "0.1.0",
|
||||
"description": "Shared modules for SQL Operations Studio extensions",
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.5",
|
||||
"decompress": "^4.2.0",
|
||||
"fs-extra-promise": "^1.0.1",
|
||||
"http-proxy-agent": "^2.0.0",
|
||||
"https-proxy-agent": "^2.1.0",
|
||||
"opener": "^1.4.3",
|
||||
"tmp": "0.0.33",
|
||||
"vscode-extension-telemetry": "0.0.8",
|
||||
"vscode-languageclient": "3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^6.0.61",
|
||||
"vscode": "^1.1.10",
|
||||
"sqlops": "github:anthonydresser/vscode-extension-vscode"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node ./node_modules/vscode/bin/install && node ./node_modules/sqlops/bin/install && tsc -p ./src",
|
||||
"compile": "tsc -p ./src",
|
||||
"watch": "tsc -w -p ./src"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "*",
|
||||
"sqlops": "*"
|
||||
},
|
||||
"main": "./lib/main.js",
|
||||
"typings": "./lib/main"
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
const fs = require('fs');
|
||||
import * as path from 'path';
|
||||
import { IConfig } from '../languageservice/interfaces';
|
||||
import { Constants } from '../models/constants';
|
||||
|
||||
/*
|
||||
* Config class handles getting values from config.json.
|
||||
*/
|
||||
export default class Config implements IConfig {
|
||||
private _configJsonContent: any = undefined;
|
||||
|
||||
private _extensionConfigSectionName: string = undefined;
|
||||
private _fromBuild: boolean = undefined;
|
||||
|
||||
constructor(extensionConfigSectionName: string, private path: string, fromBuild?: boolean) {
|
||||
this._extensionConfigSectionName = extensionConfigSectionName;
|
||||
this._fromBuild = fromBuild;
|
||||
}
|
||||
|
||||
public get configJsonContent(): any {
|
||||
if (this._configJsonContent === undefined) {
|
||||
this._configJsonContent = this.loadConfig();
|
||||
}
|
||||
return this._configJsonContent;
|
||||
}
|
||||
|
||||
public getDownloadUrl(): string {
|
||||
return this.getConfigValue(Constants.downloadUrlConfigKey);
|
||||
}
|
||||
|
||||
public getInstallDirectory(): string {
|
||||
return this.getConfigValue(Constants.installDirConfigKey);
|
||||
}
|
||||
|
||||
public getExecutableFiles(): string[] {
|
||||
return this.getConfigValue(Constants.executableFilesConfigKey);
|
||||
}
|
||||
|
||||
public getPackageVersion(): string {
|
||||
return this.getConfigValue(Constants.versionConfigKey);
|
||||
}
|
||||
|
||||
public getConfigValue(configKey: string): any {
|
||||
let json = this.configJsonContent;
|
||||
let toolsConfig = json[Constants.serviceConfigKey];
|
||||
let configValue: string = undefined;
|
||||
if (toolsConfig !== undefined) {
|
||||
configValue = toolsConfig[configKey];
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public getExtensionConfig(key: string, defaultValue?: any): any {
|
||||
let json = this.configJsonContent;
|
||||
let extensionConfig = json[this._extensionConfigSectionName];
|
||||
let configValue = extensionConfig[key];
|
||||
if (!configValue) {
|
||||
configValue = defaultValue;
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public getWorkspaceConfig(key: string, defaultValue?: any): any {
|
||||
let json = this.configJsonContent;
|
||||
let configValue = json[key];
|
||||
if (!configValue) {
|
||||
configValue = defaultValue;
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public loadConfig(): any {
|
||||
let configContent = undefined;
|
||||
if (this._fromBuild) {
|
||||
let remainingPath = '../../../extensions/' + this._extensionConfigSectionName + '/client/out/config.json';
|
||||
configContent = fs.readFileSync(path.join(__dirname, remainingPath));
|
||||
}
|
||||
else {
|
||||
configContent = fs.readFileSync(this.path);
|
||||
}
|
||||
return JSON.parse(configContent);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import Config from './config';
|
||||
import { workspace, WorkspaceConfiguration } from 'vscode';
|
||||
import { IConfig } from '../languageservice/interfaces';
|
||||
import { Constants } from '../models/constants';
|
||||
|
||||
/*
|
||||
* ExtConfig class handles getting values from workspace config or config.json.
|
||||
*/
|
||||
export default class ExtConfig implements IConfig {
|
||||
|
||||
constructor(private _extensionConfigSectionName: string, private _config?: IConfig,
|
||||
path?: string,
|
||||
private _extensionConfig?: WorkspaceConfiguration,
|
||||
private _workspaceConfig?: WorkspaceConfiguration) {
|
||||
if (this._config === undefined) {
|
||||
this._config = new Config(_extensionConfigSectionName, path);
|
||||
}
|
||||
if (this._extensionConfig === undefined) {
|
||||
this._extensionConfig = workspace.getConfiguration(_extensionConfigSectionName);
|
||||
}
|
||||
if (this._workspaceConfig === undefined) {
|
||||
this._workspaceConfig = workspace.getConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
public getDownloadUrl(): string {
|
||||
return this.getConfigValue(Constants.downloadUrlConfigKey);
|
||||
}
|
||||
|
||||
public getInstallDirectory(): string {
|
||||
return this.getConfigValue(Constants.installDirConfigKey);
|
||||
}
|
||||
|
||||
public getExecutableFiles(): string[] {
|
||||
return this.getConfigValue(Constants.executableFilesConfigKey);
|
||||
}
|
||||
|
||||
public getPackageVersion(): string {
|
||||
return this.getConfigValue(Constants.versionConfigKey);
|
||||
}
|
||||
|
||||
public getConfigValue(configKey: string): any {
|
||||
let configValue: string = <string>this.getExtensionConfig(`${Constants.serviceConfigKey}.${configKey}`);
|
||||
if (!configValue) {
|
||||
configValue = this._config.getConfigValue(configKey);
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public getExtensionConfig(key: string, defaultValue?: any): any {
|
||||
let configValue = this._extensionConfig.get(key);
|
||||
if (configValue === undefined) {
|
||||
configValue = defaultValue;
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public getWorkspaceConfig(key: string, defaultValue?: any): any {
|
||||
let configValue = this._workspaceConfig.get(key);
|
||||
if (configValue === undefined) {
|
||||
configValue = defaultValue;
|
||||
}
|
||||
return configValue;
|
||||
}
|
||||
|
||||
public updateWorkspaceConfig(configKey: string, configValue: any) {
|
||||
this._workspaceConfig.update(configKey, configValue, true);
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import vscode = require('vscode');
|
||||
import { IExtensionConstants } from '../models/contracts/contracts';
|
||||
|
||||
export class VscodeWrapper {
|
||||
private _extensionConstants: IExtensionConstants;
|
||||
/**
|
||||
* Output channel for logging. Shared among all instances.
|
||||
*/
|
||||
private static _outputChannel: vscode.OutputChannel;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public constructor(constants: IExtensionConstants) {
|
||||
this._extensionConstants = constants;
|
||||
if (typeof VscodeWrapper._outputChannel === 'undefined') {
|
||||
VscodeWrapper._outputChannel = this.createOutputChannel(this._extensionConstants.outputChannelName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current active text editor
|
||||
*/
|
||||
public get activeTextEditor(): vscode.TextEditor {
|
||||
return vscode.window.activeTextEditor;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current textDocument; any that are open?
|
||||
*/
|
||||
public get textDocuments(): vscode.TextDocument[] {
|
||||
return vscode.workspace.textDocuments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse uri
|
||||
*/
|
||||
public parseUri(uri: string): vscode.Uri {
|
||||
return vscode.Uri.parse(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URI string for the current active text editor
|
||||
*/
|
||||
public get activeTextEditorUri(): string {
|
||||
if (typeof vscode.window.activeTextEditor !== 'undefined' &&
|
||||
typeof vscode.window.activeTextEditor.document !== 'undefined') {
|
||||
return vscode.window.activeTextEditor.document.uri.toString();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get constants(): IExtensionConstants {
|
||||
return this._extensionConstants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an output channel in vscode.
|
||||
*/
|
||||
public createOutputChannel(channelName: string): vscode.OutputChannel {
|
||||
return vscode.window.createOutputChannel(channelName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the command denoted by the given command identifier.
|
||||
*
|
||||
* When executing an editor command not all types are allowed to
|
||||
* be passed as arguments. Allowed are the primitive types `string`, `boolean`,
|
||||
* `number`, `undefined`, and `null`, as well as classes defined in this API.
|
||||
* There are no restrictions when executing commands that have been contributed
|
||||
* by extensions.
|
||||
*
|
||||
* @param command Identifier of the command to execute.
|
||||
* @param rest Parameters passed to the command function.
|
||||
* @return A thenable that resolves to the returned value of the given command. `undefined` when
|
||||
* the command handler function doesn't return anything.
|
||||
* @see vscode.commands.executeCommand
|
||||
*/
|
||||
public executeCommand<T>(command: string, ...rest: any[]): Thenable<T> {
|
||||
return vscode.commands.executeCommand<T>(command, ...rest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration for a extensionName; NOT YET IMPLEMENTED
|
||||
* @param extensionName The string name of the extension to get the configuration for
|
||||
*/
|
||||
public getConfiguration(extensionName: string): vscode.WorkspaceConfiguration {
|
||||
return vscode.workspace.getConfiguration(extensionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 'true' if the active editor window has a .sql file, false otherwise
|
||||
*/
|
||||
public get isEditingSqlFile(): boolean {
|
||||
let sqlFile = false;
|
||||
let editor = this.activeTextEditor;
|
||||
if (editor) {
|
||||
if (editor.document.languageId === this._extensionConstants.languageId) {
|
||||
sqlFile = true;
|
||||
}
|
||||
}
|
||||
return sqlFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is emitted when a [text document](#TextDocument) is disposed.
|
||||
*/
|
||||
public get onDidCloseTextDocument(): vscode.Event<vscode.TextDocument> {
|
||||
return vscode.workspace.onDidCloseTextDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is emitted when a [text document](#TextDocument) is opened.
|
||||
*/
|
||||
public get onDidOpenTextDocument(): vscode.Event<vscode.TextDocument> {
|
||||
return vscode.workspace.onDidOpenTextDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event that is emitted when a [text document](#TextDocument) is saved to disk.
|
||||
*/
|
||||
public get onDidSaveTextDocument(): vscode.Event<vscode.TextDocument> {
|
||||
return vscode.workspace.onDidSaveTextDocument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the denoted document from disk. Will return early if the
|
||||
* document is already open, otherwise the document is loaded and the
|
||||
* [open document](#workspace.onDidOpenTextDocument)-event fires.
|
||||
* The document to open is denoted by the [uri](#Uri). Two schemes are supported:
|
||||
*
|
||||
* file: A file on disk, will be rejected if the file does not exist or cannot be loaded, e.g. `file:///Users/frodo/r.ini`.
|
||||
* untitled: A new file that should be saved on disk, e.g. `untitled:c:\frodo\new.js`. The language will be derived from the file name.
|
||||
*
|
||||
* Uris with other schemes will make this method return a rejected promise.
|
||||
*
|
||||
* @param uri Identifies the resource to open.
|
||||
* @return A promise that resolves to a [document](#TextDocument).
|
||||
* @see vscode.workspace.openTextDocument
|
||||
*/
|
||||
public openTextDocument(uri: vscode.Uri): Thenable<vscode.TextDocument> {
|
||||
return vscode.workspace.openTextDocument(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to log messages to output channel.
|
||||
*/
|
||||
public logToOutputChannel(msg: any): void {
|
||||
let date: Date = new Date();
|
||||
if (msg instanceof Array) {
|
||||
msg.forEach(element => {
|
||||
VscodeWrapper._outputChannel.appendLine('[' + date.toLocaleTimeString() + '] ' + element.toString());
|
||||
});
|
||||
} else {
|
||||
VscodeWrapper._outputChannel.appendLine('[' + date.toLocaleTimeString() + '] ' + msg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a vscode.Range object
|
||||
* @param start The start position for the range
|
||||
* @param end The end position for the range
|
||||
*/
|
||||
public range(start: vscode.Position, end: vscode.Position): vscode.Range {
|
||||
return new vscode.Range(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a vscode.Position object
|
||||
* @param line The line for the position
|
||||
* @param column The column for the position
|
||||
*/
|
||||
public position(line: number, column: number): vscode.Position {
|
||||
return new vscode.Position(line, column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a vscode.Selection object
|
||||
* @param start The start postion of the selection
|
||||
* @param end The end position of the selection
|
||||
*/
|
||||
public selection(start: vscode.Position, end: vscode.Position): vscode.Selection {
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats and shows a vscode error message
|
||||
*/
|
||||
public showErrorMessage(msg: string, ...items: string[]): Thenable<string> {
|
||||
return vscode.window.showErrorMessage(this._extensionConstants.extensionName + ': ' + msg, ...items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats and shows a vscode information message
|
||||
*/
|
||||
public showInformationMessage(msg: string, ...items: string[]): Thenable<string> {
|
||||
return vscode.window.showInformationMessage(this._extensionConstants.extensionName + ': ' + msg, ...items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a selection list.
|
||||
*
|
||||
* @param items An array of items, or a promise that resolves to an array of items.
|
||||
* @param options Configures the behavior of the selection list.
|
||||
* @return A promise that resolves to the selected item or undefined.
|
||||
*/
|
||||
public showQuickPick<T extends vscode.QuickPickItem>(items: T[] | Thenable<T[]>, options?: vscode.QuickPickOptions): Thenable<T> {
|
||||
return vscode.window.showQuickPick<T>(items, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the given document in a text editor. A [column](#ViewColumn) can be provided
|
||||
* to control where the editor is being shown. Might change the [active editor](#window.activeTextEditor).
|
||||
*
|
||||
* @param document A text document to be shown.
|
||||
* @param column A view column in which the editor should be shown. The default is the [one](#ViewColumn.One), other values
|
||||
* are adjusted to be __Min(column, columnCount + 1)__.
|
||||
* @param preserveFocus When `true` the editor will not take focus.
|
||||
* @return A promise that resolves to an [editor](#TextEditor).
|
||||
*/
|
||||
public showTextDocument(document: vscode.TextDocument, column?: vscode.ViewColumn, preserveFocus?: boolean): Thenable<vscode.TextEditor> {
|
||||
return vscode.window.showTextDocument(document, column, preserveFocus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats and shows a vscode warning message
|
||||
*/
|
||||
public showWarningMessage(msg: string): Thenable<string> {
|
||||
return vscode.window.showWarningMessage(this._extensionConstants.extensionName + ': ' + msg );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a array of the text editors currently visible in the window
|
||||
*/
|
||||
public get visibleEditors(): vscode.TextEditor[] {
|
||||
return vscode.window.visibleTextEditors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an URI from a file system path. The [scheme](#Uri.scheme)
|
||||
* will be `file`.
|
||||
*
|
||||
* @param path A file system or UNC path.
|
||||
* @return A new Uri instance.
|
||||
* @see vscode.Uri.file
|
||||
*/
|
||||
public uriFile(path: string): vscode.Uri {
|
||||
return vscode.Uri.file(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an URI from a string. Will throw if the given value is not
|
||||
* valid.
|
||||
*
|
||||
* @param value The string value of an Uri.
|
||||
* @return A new Uri instance.
|
||||
* @see vscode.Uri.parse
|
||||
*/
|
||||
public uriParse(value: string): vscode.Uri {
|
||||
return vscode.Uri.parse(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The folder that is open in VS Code. `undefined` when no folder
|
||||
* has been opened.
|
||||
*
|
||||
* @readonly
|
||||
* @see vscode.workspace.rootPath
|
||||
*/
|
||||
public get workspaceRootPath(): string {
|
||||
return vscode.workspace.rootPath;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IDecompressProvider, IPackage } from './interfaces';
|
||||
import { ILogger } from '../models/interfaces';
|
||||
const decompress = require('decompress');
|
||||
|
||||
export default class DecompressProvider implements IDecompressProvider {
|
||||
public decompress(pkg: IPackage, logger: ILogger): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
decompress(pkg.tmpFile.name, pkg.installPath).then(files => {
|
||||
logger.appendLine(`Done! ${files.length} files unpacked.\n`);
|
||||
resolve();
|
||||
}).catch(decompressErr => {
|
||||
logger.appendLine(`[ERROR] ${decompressErr}`);
|
||||
reject(decompressErr);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { IPackage, IStatusView, PackageError, IHttpClient } from './interfaces';
|
||||
import { ILogger } from '../models/interfaces';
|
||||
import { parse as parseUrl, Url } from 'url';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import { getProxyAgent } from './proxy';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
/*
|
||||
* Http client class to handle downloading files using http or https urls
|
||||
*/
|
||||
export default class HttpClient implements IHttpClient {
|
||||
|
||||
/*
|
||||
* Downloads a file and stores the result in the temp file inside the package object
|
||||
*/
|
||||
public downloadFile(urlString: string, pkg: IPackage, logger: ILogger, statusView: IStatusView, proxy?: string, strictSSL?: boolean): Promise<void> {
|
||||
const url = parseUrl(urlString);
|
||||
let options = this.getHttpClientOptions(url, proxy, strictSSL);
|
||||
let clientRequest = url.protocol === 'http:' ? http.request : https.request;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!pkg.tmpFile || pkg.tmpFile.fd === 0) {
|
||||
return reject(new PackageError('Temporary package file unavailable', pkg));
|
||||
}
|
||||
|
||||
let request = clientRequest(options, response => {
|
||||
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||
// Redirect - download from new location
|
||||
return resolve(this.downloadFile(response.headers.location, pkg, logger, statusView, proxy, strictSSL));
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
// Download failed - print error message
|
||||
logger.appendLine(`failed (error code '${response.statusCode}')`);
|
||||
return reject(new PackageError(response.statusCode.toString(), pkg));
|
||||
}
|
||||
|
||||
// If status code is 200
|
||||
this.handleSuccessfulResponse(pkg, response, logger, statusView).then(_ => {
|
||||
resolve();
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
request.on('error', error => {
|
||||
// reject(new PackageError(`Request error: ${error.code || 'NONE'}`, pkg, error));
|
||||
reject(new PackageError(`Request error: ${error.name || 'NONE'}`, pkg, error));
|
||||
});
|
||||
|
||||
// Execute the request
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
private getHttpClientOptions(url: Url, proxy?: string, strictSSL?: boolean): any {
|
||||
const agent = getProxyAgent(url, proxy, strictSSL);
|
||||
|
||||
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 options;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the download percentage and stores in the progress object
|
||||
*/
|
||||
public handleDataReceivedEvent(progress: IDownloadProgress, data: any, logger: ILogger, statusView: IStatusView): void {
|
||||
progress.downloadedBytes += data.length;
|
||||
|
||||
// Update status bar item with percentage
|
||||
if (progress.packageSize > 0) {
|
||||
let newPercentage = Math.ceil(100 * (progress.downloadedBytes / progress.packageSize));
|
||||
if (newPercentage !== progress.downloadPercentage) {
|
||||
statusView.updateServiceDownloadingProgress(progress.downloadPercentage);
|
||||
progress.downloadPercentage = newPercentage;
|
||||
}
|
||||
|
||||
// Update dots after package name in output console
|
||||
let newDots = Math.ceil(progress.downloadPercentage / 5);
|
||||
if (newDots > progress.dots) {
|
||||
logger.append('.'.repeat(newDots - progress.dots));
|
||||
progress.dots = newDots;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
private handleSuccessfulResponse(pkg: IPackage, response: http.IncomingMessage, logger: ILogger, statusView: IStatusView): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let progress: IDownloadProgress = {
|
||||
packageSize: parseInt(response.headers['content-length'], 10),
|
||||
dots: 0,
|
||||
downloadedBytes: 0,
|
||||
downloadPercentage: 0
|
||||
};
|
||||
logger.append(`(${Math.ceil(progress.packageSize / 1024)} KB) `);
|
||||
response.on('data', data => {
|
||||
this.handleDataReceivedEvent(progress, data, logger, statusView);
|
||||
});
|
||||
let tmpFile = fs.createWriteStream(undefined, { fd: pkg.tmpFile.fd });
|
||||
response.on('end', () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
response.on('error', err => {
|
||||
reject(new PackageError(`Response error: ${err.name || 'NONE'}`, pkg, err));
|
||||
});
|
||||
|
||||
// Begin piping data from the response to the package file
|
||||
response.pipe(tmpFile, { end: false });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Interface to store the values needed to calculate download percentage
|
||||
*/
|
||||
export interface IDownloadProgress {
|
||||
packageSize: number;
|
||||
downloadedBytes: number;
|
||||
downloadPercentage: number;
|
||||
dots: number;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as tmp from 'tmp';
|
||||
import { ILogger } from '../models/interfaces';
|
||||
|
||||
export interface IStatusView {
|
||||
installingService(): void;
|
||||
serviceInstalled(): void;
|
||||
serviceInstallationFailed(): void;
|
||||
updateServiceDownloadingProgress(downloadPercentage: number): void;
|
||||
}
|
||||
|
||||
export interface IConfig {
|
||||
getDownloadUrl(): string;
|
||||
getInstallDirectory(): string;
|
||||
getExecutableFiles(): string[];
|
||||
getPackageVersion(): string;
|
||||
getExtensionConfig(key: string, defaultValue?: any): any;
|
||||
getWorkspaceConfig(key: string, defaultValue?: any): any;
|
||||
getConfigValue(configKey: string): any;
|
||||
}
|
||||
|
||||
export interface IPackage {
|
||||
url: string;
|
||||
installPath?: string;
|
||||
tmpFile: tmp.SynchronousResult;
|
||||
}
|
||||
|
||||
export class PackageError extends Error {
|
||||
// Do not put PII (personally identifiable information) in the 'message' field as it will be logged to telemetry
|
||||
constructor(public message: string,
|
||||
public pkg: IPackage = undefined,
|
||||
public innerError: any = undefined) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IHttpClient {
|
||||
downloadFile(urlString: string, pkg: IPackage, logger: ILogger, statusView: IStatusView, proxy: string, strictSSL: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IDecompressProvider {
|
||||
decompress(pkg: IPackage, logger: ILogger): Promise<void>;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Url, parse as parseUrl } from 'url';
|
||||
const HttpProxyAgent = require('http-proxy-agent');
|
||||
const 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);
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import { Runtime } from '../models/platform';
|
||||
import ServiceDownloadProvider from './serviceDownloadProvider';
|
||||
import { IConfig, IStatusView } from './interfaces';
|
||||
const 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: IConfig,
|
||||
private _statusView: IStatusView,
|
||||
private _extensionConfigSectionName: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public get method for downloadProvider
|
||||
*/
|
||||
public get downloadProvider(): ServiceDownloadProvider {
|
||||
return this._downloadProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a file path, returns the path to the SQL Tools service file.
|
||||
*/
|
||||
public findServerPath(filePath: string, executableFiles: string[] = undefined): 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 (executableFiles === undefined && this._config !== undefined) {
|
||||
executableFiles = this._config.getExecutableFiles();
|
||||
}
|
||||
if (executableFiles !== undefined) {
|
||||
executableFiles.forEach(element => {
|
||||
let executableFile = path.join(filePath, element);
|
||||
if (candidate === undefined && fs.existsSync(executableFile)) {
|
||||
candidate = executableFile;
|
||||
return candidate;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return candidate;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the service if doesn't exist and returns the file path.
|
||||
*/
|
||||
public getOrDownloadServer(runtime: Runtime): 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.
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
return this.getServerPath(runtime).then(result => {
|
||||
if (result === undefined) {
|
||||
return this.downloadServerFiles(runtime).then ( downloadResult => {
|
||||
resolve(downloadResult);
|
||||
});
|
||||
} else {
|
||||
return resolve(result);
|
||||
}
|
||||
}).catch(err => {
|
||||
return reject(err);
|
||||
});
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the installed service
|
||||
*/
|
||||
public getServerPath(runtime: Runtime): Promise<string> {
|
||||
const installDirectory = this._downloadProvider.getInstallDirectory(runtime, this._extensionConfigSectionName);
|
||||
return this.findServerPath(installDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the service and returns the path of the installed service
|
||||
*/
|
||||
public downloadServerFiles(runtime: Runtime): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const installDirectory = this._downloadProvider.getInstallDirectory(runtime, this._extensionConfigSectionName);
|
||||
return this._downloadProvider.installService(runtime).then( _ => {
|
||||
return this.findServerPath(installDirectory).then ( result => {
|
||||
return resolve(result);
|
||||
});
|
||||
}).catch(err => {
|
||||
this._statusView.serviceInstallationFailed();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IStatusView } from './interfaces';
|
||||
import vscode = require('vscode');
|
||||
import { IExtensionConstants } from '../models/contracts/contracts';
|
||||
import { Constants } from '../models/constants';
|
||||
|
||||
/*
|
||||
* The status class which includes the service initialization result.
|
||||
*/
|
||||
export class ServerInitializationResult {
|
||||
|
||||
public constructor(
|
||||
public installedBeforeInitializing: Boolean = false,
|
||||
public isRunning: Boolean = false,
|
||||
public serverPath: string = undefined
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public Clone(): ServerInitializationResult {
|
||||
return new ServerInitializationResult(this.installedBeforeInitializing, this.isRunning, this.serverPath);
|
||||
}
|
||||
|
||||
public WithRunning(isRunning: Boolean): ServerInitializationResult {
|
||||
return new ServerInitializationResult(this.installedBeforeInitializing, isRunning, this.serverPath);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The status class shows service installing progress in UI
|
||||
*/
|
||||
export class ServerStatusView implements IStatusView, vscode.Disposable {
|
||||
private _numberOfSecondsBeforeHidingMessage = 5000;
|
||||
private _statusBarItem: vscode.StatusBarItem = undefined;
|
||||
private _progressTimerId: NodeJS.Timer;
|
||||
private _constants: IExtensionConstants;
|
||||
|
||||
constructor(constants: IExtensionConstants) {
|
||||
this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
vscode.window.onDidChangeActiveTextEditor((params) => this.onDidChangeActiveTextEditor(params));
|
||||
vscode.workspace.onDidCloseTextDocument((params) => this.onDidCloseTextDocument(params));
|
||||
this._constants = constants;
|
||||
}
|
||||
|
||||
public installingService(): void {
|
||||
this._statusBarItem.command = undefined;
|
||||
this._statusBarItem.show();
|
||||
|
||||
this.showProgress('$(desktop-download) ' + Constants.serviceInstalling);
|
||||
}
|
||||
|
||||
public updateServiceDownloadingProgress(downloadPercentage: number): void {
|
||||
this._statusBarItem.text = '$(cloud-download) ' + `${Constants.serviceDownloading} ... ${downloadPercentage}%`;
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
|
||||
public serviceInstalled(): void {
|
||||
|
||||
this._statusBarItem.command = undefined;
|
||||
this._statusBarItem.text = this._constants.serviceInstalled;
|
||||
this._statusBarItem.show();
|
||||
// Cleat the status bar after 2 seconds
|
||||
setTimeout(() => {
|
||||
this._statusBarItem.hide();
|
||||
}, this._numberOfSecondsBeforeHidingMessage);
|
||||
}
|
||||
|
||||
public serviceInstallationFailed(): void {
|
||||
this._statusBarItem.command = undefined;
|
||||
this._statusBarItem.text = this._constants.serviceInstallationFailed;
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
|
||||
private showProgress(statusText: string): void {
|
||||
let index = 0;
|
||||
let progressTicks = [ '|', '/', '-', '\\'];
|
||||
|
||||
|
||||
this._progressTimerId = setInterval(() => {
|
||||
index++;
|
||||
if (index > 3) {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
let progressTick = progressTicks[index];
|
||||
if (this._statusBarItem.text !== this._constants.serviceInstalled) {
|
||||
this._statusBarItem.text = statusText + ' ' + progressTick;
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.destroyStatusBar();
|
||||
}
|
||||
|
||||
private hideLastShownStatusBar(): void {
|
||||
if (typeof this._statusBarItem !== 'undefined') {
|
||||
this._statusBarItem.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeActiveTextEditor(editor: vscode.TextEditor): void {
|
||||
// Hide the most recently shown status bar
|
||||
this.hideLastShownStatusBar();
|
||||
}
|
||||
|
||||
private onDidCloseTextDocument(doc: vscode.TextDocument): void {
|
||||
// Remove the status bar associated with the document
|
||||
this.destroyStatusBar();
|
||||
}
|
||||
|
||||
private destroyStatusBar(): void {
|
||||
if (typeof this._statusBarItem !== 'undefined') {
|
||||
this._statusBarItem.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,535 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ExtensionContext, workspace, window, OutputChannel, languages } from 'vscode';
|
||||
import * as SqlopsClient from 'dataprotocol-client';
|
||||
import { CloseAction, ErrorAction, ServerOptions, NotificationHandler, NotificationType, RequestType, TransportKind } from 'vscode-languageclient';
|
||||
|
||||
import { VscodeWrapper } from '../controllers/vscodeWrapper';
|
||||
import { Telemetry } from '../models/telemetry';
|
||||
import { Utils } from '../models/utils';
|
||||
import { VersionRequest, IExtensionConstants } from '../models/contracts/contracts';
|
||||
import { Logger } from '../models/logger';
|
||||
import ServerProvider from './server';
|
||||
import ServiceDownloadProvider from './serviceDownloadProvider';
|
||||
import DecompressProvider from './decompressProvider';
|
||||
import HttpClient from './httpClient';
|
||||
import ExtConfig from '../configurations/extConfig';
|
||||
import { PlatformInformation, Runtime } from '../models/platform';
|
||||
import { ServerInitializationResult, ServerStatusView } from './serverStatus';
|
||||
import StatusView from '../views/statusView';
|
||||
import * as LanguageServiceContracts from '../models/contracts/languageService';
|
||||
import { Constants } from '../models/constants';
|
||||
import ServiceStatus from './serviceStatus';
|
||||
|
||||
const opener = require('opener');
|
||||
const path = require('path');
|
||||
let _channel: OutputChannel = undefined;
|
||||
|
||||
/**
|
||||
* @interface IMessage
|
||||
*/
|
||||
interface IMessage {
|
||||
jsonrpc: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Language Service client errors
|
||||
* @class LanguageClientErrorHandler
|
||||
*/
|
||||
class LanguageClientErrorHandler {
|
||||
|
||||
private vscodeWrapper: VscodeWrapper;
|
||||
|
||||
/**
|
||||
* Creates an instance of LanguageClientErrorHandler.
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
constructor(constants: IExtensionConstants) {
|
||||
if (!this.vscodeWrapper) {
|
||||
this.vscodeWrapper = new VscodeWrapper(constants);
|
||||
}
|
||||
Telemetry.getRuntimeId = this.vscodeWrapper.constants.getRuntimeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error message prompt with a link to known issues wiki page
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
showOnErrorPrompt(): void {
|
||||
let extensionConstants = this.vscodeWrapper.constants;
|
||||
Telemetry.sendTelemetryEvent(extensionConstants.serviceName + 'Crash');
|
||||
this.vscodeWrapper.showErrorMessage(
|
||||
extensionConstants.serviceCrashMessage,
|
||||
Constants.serviceCrashButton).then(action => {
|
||||
if (action && action === Constants.serviceCrashButton) {
|
||||
opener(extensionConstants.serviceCrashLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client error
|
||||
*
|
||||
* @param {Error} error
|
||||
* @param {Message} message
|
||||
* @param {number} count
|
||||
* @returns {ErrorAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
error(error: Error, message: IMessage, count: number): ErrorAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return ErrorAction.Shutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client closed
|
||||
*
|
||||
* @returns {CloseAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
closed(): CloseAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return CloseAction.DoNotRestart;
|
||||
}
|
||||
}
|
||||
|
||||
// The Service Client class handles communication with the VS Code LanguageClient
|
||||
export class SqlToolsServiceClient {
|
||||
// singleton instance
|
||||
private static _instance: SqlToolsServiceClient = undefined;
|
||||
|
||||
private static _constants: IExtensionConstants = undefined;
|
||||
|
||||
public static get constants(): IExtensionConstants {
|
||||
return this._constants;
|
||||
}
|
||||
|
||||
public static set constants(constantsObject: IExtensionConstants) {
|
||||
this._constants = constantsObject;
|
||||
Telemetry.getRuntimeId = this._constants.getRuntimeId;
|
||||
}
|
||||
|
||||
private static _helper: LanguageServiceContracts.ILanguageClientHelper = undefined;
|
||||
|
||||
public static get helper(): LanguageServiceContracts.ILanguageClientHelper {
|
||||
return this._helper;
|
||||
}
|
||||
|
||||
public static set helper(helperObject: LanguageServiceContracts.ILanguageClientHelper) {
|
||||
this._helper = helperObject;
|
||||
}
|
||||
|
||||
// VS Code Language Client
|
||||
private _client: SqlopsClient.SqlOpsDataClient = undefined;
|
||||
|
||||
// getter method for the Language Client
|
||||
private get client(): SqlopsClient.SqlOpsDataClient {
|
||||
return this._client;
|
||||
}
|
||||
|
||||
private set client(client: SqlopsClient.SqlOpsDataClient) {
|
||||
this._client = client;
|
||||
}
|
||||
|
||||
public installDirectory: string;
|
||||
private _downloadProvider: ServiceDownloadProvider;
|
||||
private _vscodeWrapper: VscodeWrapper;
|
||||
|
||||
private _serviceStatus: ServiceStatus;
|
||||
|
||||
private _languageClientStartTime: number = undefined;
|
||||
private _installationTime: number = undefined;
|
||||
|
||||
constructor(
|
||||
private _server: ServerProvider,
|
||||
private _logger: Logger,
|
||||
private _statusView: StatusView,
|
||||
private _config: ExtConfig) {
|
||||
this._downloadProvider = _server.downloadProvider;
|
||||
if (!this._vscodeWrapper) {
|
||||
this._vscodeWrapper = new VscodeWrapper(SqlToolsServiceClient.constants);
|
||||
}
|
||||
this._serviceStatus = new ServiceStatus(SqlToolsServiceClient._constants.serviceName);
|
||||
}
|
||||
|
||||
// gets or creates the singleton service client instance
|
||||
public static getInstance(path: string): SqlToolsServiceClient {
|
||||
if (this._instance === undefined) {
|
||||
let constants = this._constants;
|
||||
let config = new ExtConfig(constants.extensionConfigSectionName, undefined, path);
|
||||
_channel = window.createOutputChannel(constants.serviceInitializingOutputChannelName);
|
||||
let logger = new Logger(text => _channel.append(text), constants);
|
||||
let serverStatusView = new ServerStatusView(constants);
|
||||
let httpClient = new HttpClient();
|
||||
let decompressProvider = new DecompressProvider();
|
||||
let downloadProvider = new ServiceDownloadProvider(config, logger, serverStatusView, httpClient,
|
||||
decompressProvider, constants, false);
|
||||
let serviceProvider = new ServerProvider(downloadProvider, config, serverStatusView, constants.extensionConfigSectionName);
|
||||
let statusView = new StatusView();
|
||||
this._instance = new SqlToolsServiceClient(serviceProvider, logger, statusView, config);
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
// initialize the Service Client instance by launching
|
||||
// out-of-proc server through the LanguageClient
|
||||
public initialize(context: ExtensionContext): Promise<any> {
|
||||
this._logger.appendLine(SqlToolsServiceClient._constants.serviceInitializing);
|
||||
this._languageClientStartTime = Date.now();
|
||||
return PlatformInformation.getCurrent(SqlToolsServiceClient._constants.getRuntimeId, SqlToolsServiceClient._constants.extensionName).then(platformInfo => {
|
||||
return this.initializeForPlatform(platformInfo, context);
|
||||
}).catch(err => {
|
||||
this._vscodeWrapper.showErrorMessage(err);
|
||||
});
|
||||
}
|
||||
|
||||
public initializeForPlatform(platformInfo: PlatformInformation, context: ExtensionContext): Promise<ServerInitializationResult> {
|
||||
return new Promise<ServerInitializationResult>((resolve, reject) => {
|
||||
this._logger.appendLine(SqlToolsServiceClient._constants.commandsNotAvailableWhileInstallingTheService);
|
||||
this._logger.appendLine();
|
||||
this._logger.append(`Platform: ${platformInfo.toString()}`);
|
||||
|
||||
if (!platformInfo.isValidRuntime()) {
|
||||
// if it's an unknown Linux distro then try generic Linux x64 and give a warning to the user
|
||||
if (platformInfo.isLinux()) {
|
||||
this._logger.appendLine(Constants.usingDefaultPlatformMessage);
|
||||
platformInfo.runtimeId = Runtime.Linux_64;
|
||||
}
|
||||
|
||||
let ignoreWarning: boolean = this._config.getWorkspaceConfig(Constants.ignorePlatformWarning, false);
|
||||
if (!ignoreWarning) {
|
||||
this._vscodeWrapper.showErrorMessage(
|
||||
Constants.unsupportedPlatformErrorMessage,
|
||||
Constants.neverShowAgain)
|
||||
.then(action => {
|
||||
if (action === Constants.neverShowAgain) {
|
||||
this._config.updateWorkspaceConfig(Constants.ignorePlatformWarning, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Telemetry.sendTelemetryEvent('UnsupportedPlatform', { platform: platformInfo.toString() });
|
||||
}
|
||||
|
||||
if (platformInfo.runtimeId) {
|
||||
this._logger.appendLine(` (${platformInfo.getRuntimeDisplayName()})`);
|
||||
} else {
|
||||
this._logger.appendLine();
|
||||
}
|
||||
|
||||
this._logger.appendLine();
|
||||
|
||||
this._server.getServerPath(platformInfo.runtimeId).then(serverPath => {
|
||||
if (serverPath === undefined) {
|
||||
// Check if the service already installed and if not open the output channel to show the logs
|
||||
if (_channel !== undefined) {
|
||||
_channel.show();
|
||||
}
|
||||
let installationStartTime = Date.now();
|
||||
this._server.downloadServerFiles(platformInfo.runtimeId).then(installedServerPath => {
|
||||
this._installationTime = Date.now() - installationStartTime;
|
||||
this.initializeLanguageClient(installedServerPath, context, platformInfo.runtimeId);
|
||||
resolve(new ServerInitializationResult(true, true, installedServerPath));
|
||||
}).catch(downloadErr => {
|
||||
reject(downloadErr);
|
||||
});
|
||||
} else {
|
||||
this.initializeLanguageClient(serverPath, context, platformInfo.runtimeId);
|
||||
resolve(new ServerInitializationResult(false, true, serverPath));
|
||||
}
|
||||
}).catch(err => {
|
||||
Utils.logDebug(SqlToolsServiceClient._constants.serviceLoadingFailed + ' ' + err, SqlToolsServiceClient._constants.extensionConfigSectionName);
|
||||
Utils.showErrorMsg(SqlToolsServiceClient._constants.serviceLoadingFailed, SqlToolsServiceClient._constants.extensionName);
|
||||
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the SQL language configuration
|
||||
*
|
||||
* @memberOf SqlToolsServiceClient
|
||||
*/
|
||||
private initializeLanguageConfiguration(): void {
|
||||
languages.setLanguageConfiguration('sql', {
|
||||
comments: {
|
||||
lineComment: '--',
|
||||
blockComment: ['/*', '*/']
|
||||
},
|
||||
|
||||
brackets: [
|
||||
['{', '}'],
|
||||
['[', ']'],
|
||||
['(', ')']
|
||||
],
|
||||
|
||||
__characterPairSupport: {
|
||||
autoClosingPairs: [
|
||||
{ open: '{', close: '}' },
|
||||
{ open: '[', close: ']' },
|
||||
{ open: '(', close: ')' },
|
||||
{ open: '"', close: '"', notIn: ['string'] },
|
||||
{ open: '\'', close: '\'', notIn: ['string', 'comment'] }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private initializeLanguageClient(serverPath: string, context: ExtensionContext, runtimeId: Runtime): void {
|
||||
if (serverPath === undefined) {
|
||||
Utils.logDebug(SqlToolsServiceClient._constants.invalidServiceFilePath, SqlToolsServiceClient._constants.extensionConfigSectionName);
|
||||
throw new Error(SqlToolsServiceClient._constants.invalidServiceFilePath);
|
||||
} else {
|
||||
let self = this;
|
||||
|
||||
if (SqlToolsServiceClient._constants.languageId === 'sql') {
|
||||
self.initializeLanguageConfiguration();
|
||||
}
|
||||
|
||||
// Use default createServerOptions if one isn't specified
|
||||
let serverOptions: ServerOptions = SqlToolsServiceClient._helper ?
|
||||
SqlToolsServiceClient._helper.createServerOptions(serverPath, runtimeId) : self.createServerOptions(serverPath);
|
||||
this.client = this.createLanguageClient(serverOptions);
|
||||
this.installDirectory = this._downloadProvider.getInstallDirectory(runtimeId, SqlToolsServiceClient._constants.extensionConfigSectionName);
|
||||
|
||||
if (context !== undefined) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createClient(context: ExtensionContext, runtimeId: Runtime, languageClientHelper: LanguageServiceContracts.ILanguageClientHelper, executableFiles: string[]): Promise<SqlopsClient.SqlOpsDataClient> {
|
||||
return new Promise<SqlopsClient.SqlOpsDataClient>((resolve, reject) => {
|
||||
let client: SqlopsClient.SqlOpsDataClient;
|
||||
this._server.findServerPath(this.installDirectory, executableFiles).then(serverPath => {
|
||||
if (serverPath === undefined) {
|
||||
reject(new Error(SqlToolsServiceClient._constants.invalidServiceFilePath));
|
||||
} else {
|
||||
|
||||
let serverOptions: ServerOptions = languageClientHelper ?
|
||||
languageClientHelper.createServerOptions(serverPath, runtimeId) : this.createServerOptions(serverPath);
|
||||
|
||||
// Options to control the language client
|
||||
let clientOptions: SqlopsClient.ClientOptions = {
|
||||
documentSelector: [SqlToolsServiceClient._constants.languageId],
|
||||
providerId: '',
|
||||
synchronize: {
|
||||
configurationSection: SqlToolsServiceClient._constants.extensionConfigSectionName
|
||||
},
|
||||
errorHandler: new LanguageClientErrorHandler(SqlToolsServiceClient._constants),
|
||||
outputChannel: {
|
||||
append: () => {
|
||||
},
|
||||
appendLine: () => {
|
||||
},
|
||||
dispose: () => {
|
||||
},
|
||||
clear: () => {
|
||||
},
|
||||
hide: () => {
|
||||
},
|
||||
name: '',
|
||||
show: () => {
|
||||
}
|
||||
},
|
||||
// pass in no features so we don't register features we don't want
|
||||
features: []
|
||||
};
|
||||
|
||||
this._serviceStatus.showServiceLoading();
|
||||
// cache the client instance for later use
|
||||
client = new SqlopsClient.SqlOpsDataClient(SqlToolsServiceClient._constants.serviceName, serverOptions, clientOptions);
|
||||
|
||||
if (context !== undefined) {
|
||||
// Create the language client and start the client.
|
||||
let disposable = client.start();
|
||||
|
||||
// Push the disposable to the context's subscriptions so that the
|
||||
// client can be deactivated on extension deactivation
|
||||
|
||||
context.subscriptions.push(disposable);
|
||||
}
|
||||
client.onReady().then(this._serviceStatus.showServiceLoaded);
|
||||
|
||||
resolve(client);
|
||||
}
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private createServerOptions(servicePath): ServerOptions {
|
||||
let serverArgs = [];
|
||||
let serverCommand: string = servicePath;
|
||||
if (servicePath.endsWith('.dll')) {
|
||||
serverArgs = [servicePath];
|
||||
serverCommand = 'dotnet';
|
||||
}
|
||||
|
||||
// Enable diagnostic logging in the service if it is configured
|
||||
let config = workspace.getConfiguration(SqlToolsServiceClient._constants.extensionConfigSectionName);
|
||||
if (config) {
|
||||
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||
if (logDebugInfo) {
|
||||
serverArgs.push('--enable-logging');
|
||||
}
|
||||
}
|
||||
serverArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), SqlToolsServiceClient.constants.extensionName);
|
||||
serverArgs.push(logFileLocation);
|
||||
|
||||
// run the service host using dotnet.exe from the path
|
||||
let serverOptions: ServerOptions = { command: serverCommand, args: serverArgs, transport: TransportKind.stdio };
|
||||
return serverOptions;
|
||||
}
|
||||
|
||||
private createLanguageClient(serverOptions: ServerOptions): SqlopsClient.SqlOpsDataClient {
|
||||
// Options to control the language client
|
||||
let clientOptions: SqlopsClient.ClientOptions = {
|
||||
documentSelector: [SqlToolsServiceClient._constants.languageId],
|
||||
providerId: SqlToolsServiceClient._constants.providerId,
|
||||
synchronize: {
|
||||
configurationSection: SqlToolsServiceClient._constants.extensionConfigSectionName
|
||||
},
|
||||
errorHandler: new LanguageClientErrorHandler(SqlToolsServiceClient._constants),
|
||||
outputChannel: {
|
||||
append: () => {
|
||||
},
|
||||
appendLine: () => {
|
||||
},
|
||||
dispose: () => {
|
||||
},
|
||||
clear: () => {
|
||||
},
|
||||
hide: () => {
|
||||
},
|
||||
name: '',
|
||||
show: () => {
|
||||
}
|
||||
},
|
||||
features: [
|
||||
SqlopsClient.AdminServicesFeature,
|
||||
SqlopsClient.BackupFeature,
|
||||
SqlopsClient.CapabilitiesFeature,
|
||||
SqlopsClient.ConnectionFeature,
|
||||
SqlopsClient.FileBrowserFeature,
|
||||
SqlopsClient.MetadataFeature,
|
||||
SqlopsClient.ObjectExplorerFeature,
|
||||
SqlopsClient.ProfilerFeature,
|
||||
SqlopsClient.QueryFeature,
|
||||
SqlopsClient.RestoreFeature,
|
||||
SqlopsClient.ScriptingFeature,
|
||||
SqlopsClient.TaskServicesFeature,
|
||||
// heres the important bit
|
||||
LanguageServiceContracts.AgentServicesFeature
|
||||
]
|
||||
};
|
||||
|
||||
this._serviceStatus.showServiceLoading();
|
||||
// cache the client instance for later use
|
||||
let client = new SqlopsClient.SqlOpsDataClient(SqlToolsServiceClient._constants.serviceName, serverOptions, clientOptions);
|
||||
client.onReady().then(() => {
|
||||
this.checkServiceCompatibility();
|
||||
this._serviceStatus.showServiceLoaded();
|
||||
client.onNotification(LanguageServiceContracts.TelemetryNotification.type, this.handleLanguageServiceTelemetryNotification());
|
||||
client.onNotification(LanguageServiceContracts.StatusChangedNotification.type, this.handleLanguageServiceStatusNotification());
|
||||
|
||||
// Report the language client startup time
|
||||
let endTime = Date.now();
|
||||
let installationTime = this._installationTime || 0;
|
||||
let totalTime = endTime - this._languageClientStartTime;
|
||||
let processStartupTime = totalTime - installationTime;
|
||||
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||
installationTime: String(installationTime),
|
||||
processStartupTime: String(processStartupTime),
|
||||
totalTime: String(totalTime),
|
||||
beginningTimestamp: String(this._languageClientStartTime)
|
||||
});
|
||||
this._languageClientStartTime = undefined;
|
||||
this._installationTime = undefined;
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private handleLanguageServiceTelemetryNotification(): NotificationHandler<LanguageServiceContracts.TelemetryParams> {
|
||||
return (event: LanguageServiceContracts.TelemetryParams): void => {
|
||||
Telemetry.sendTelemetryEvent(event.params.eventName, event.params.properties, event.params.measures);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Public for testing purposes only.
|
||||
*/
|
||||
public handleLanguageServiceStatusNotification(): NotificationHandler<LanguageServiceContracts.StatusChangeParams> {
|
||||
return (event: LanguageServiceContracts.StatusChangeParams): void => {
|
||||
this._statusView.languageServiceStatusChanged(event.ownerUri, event.status);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the service client
|
||||
* @param type The of the request to make
|
||||
* @param params The params to pass with the request
|
||||
* @returns A thenable object for when the request receives a response
|
||||
*/
|
||||
public sendRequest<P, R, E, RO>(type: RequestType<P, R, E, RO>, params?: P, client: SqlopsClient.SqlOpsDataClient = undefined): Thenable<R> {
|
||||
if (client === undefined) {
|
||||
client = this._client;
|
||||
}
|
||||
if (client !== undefined) {
|
||||
return client.sendRequest(type, params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler for a notification type
|
||||
* @param type The notification type to register the handler for
|
||||
* @param handler The handler to register
|
||||
*/
|
||||
public onNotification<P, RO>(type: NotificationType<P, RO>, handler: NotificationHandler<P>, client: SqlopsClient.SqlOpsDataClient = undefined): void {
|
||||
if (client === undefined) {
|
||||
client = this._client;
|
||||
}
|
||||
if (client !== undefined) {
|
||||
return client.onNotification(type, handler);
|
||||
}
|
||||
}
|
||||
|
||||
public checkServiceCompatibility(): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
this._client.sendRequest(VersionRequest.type, undefined).then((result) => {
|
||||
Utils.logDebug(SqlToolsServiceClient._constants.extensionName + ' service client version: ' + result, SqlToolsServiceClient._constants.extensionConfigSectionName);
|
||||
|
||||
if (result === undefined || !result.startsWith(SqlToolsServiceClient._constants.serviceCompatibleVersion)) {
|
||||
Utils.showErrorMsg(Constants.serviceNotCompatibleError, SqlToolsServiceClient._constants.extensionName);
|
||||
Utils.logDebug(Constants.serviceNotCompatibleError, SqlToolsServiceClient._constants.extensionConfigSectionName);
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Runtime, getRuntimeDisplayName } from '../models/platform';
|
||||
import * as path from 'path';
|
||||
import { IConfig, IStatusView, IPackage, PackageError, IHttpClient, IDecompressProvider } from './interfaces';
|
||||
import { ILogger } from '../models/interfaces';
|
||||
import { Constants } from '../models/constants';
|
||||
import * as tmp from 'tmp';
|
||||
import { IExtensionConstants } from '../models/contracts/contracts';
|
||||
|
||||
const fse = require('fs-extra');
|
||||
|
||||
/*
|
||||
* Service Download Provider class which handles downloading the SQL Tools service.
|
||||
*/
|
||||
export default class ServiceDownloadProvider {
|
||||
|
||||
constructor(private _config: IConfig,
|
||||
private _logger: ILogger,
|
||||
private _statusView: IStatusView,
|
||||
private _httpClient: IHttpClient,
|
||||
private _decompressProvider: IDecompressProvider,
|
||||
private _extensionConstants: IExtensionConstants,
|
||||
private _fromBuild: boolean) {
|
||||
// Ensure our temp files get cleaned up in case of error.
|
||||
tmp.setGracefulCleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the download url for given platform
|
||||
*/
|
||||
public getDownloadFileName(platform: Runtime): string {
|
||||
let fileNamesJson = this._config.getConfigValue('downloadFileNames');
|
||||
console.info('Platform: ', platform.toString());
|
||||
|
||||
let fileName = fileNamesJson[platform.toString()];
|
||||
console.info('Filename: ', fileName);
|
||||
|
||||
if (fileName === undefined) {
|
||||
if (process.platform === 'linux') {
|
||||
throw new Error('Unsupported linux distribution');
|
||||
} else {
|
||||
throw new Error(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns SQL tools service installed folder.
|
||||
*/
|
||||
public getInstallDirectory(platform: Runtime, extensionConfigSectionName: string): string {
|
||||
let basePath = this.getInstallDirectoryRoot(platform, extensionConfigSectionName);
|
||||
let versionFromConfig = this._config.getPackageVersion();
|
||||
basePath = basePath.replace('{#version#}', versionFromConfig);
|
||||
basePath = basePath.replace('{#platform#}', getRuntimeDisplayName(platform));
|
||||
if (!fse.existsSync(basePath)) {
|
||||
fse.mkdirsSync(basePath);
|
||||
}
|
||||
|
||||
return basePath;
|
||||
}
|
||||
|
||||
private getLocalUserFolderPath(platform: Runtime): string {
|
||||
if (platform) {
|
||||
switch (platform) {
|
||||
case Runtime.Windows_64:
|
||||
case Runtime.Windows_86:
|
||||
return process.env.APPDATA;
|
||||
case Runtime.OSX:
|
||||
return process.env.HOME + '/Library/Preferences';
|
||||
default:
|
||||
return process.env.HOME;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns SQL tools service installed folder root.
|
||||
*/
|
||||
public getInstallDirectoryRoot(platform: Runtime, extensionConfigSectionName: string): string {
|
||||
let installDirFromConfig : string;
|
||||
installDirFromConfig = this._config.getInstallDirectory();
|
||||
if (!installDirFromConfig || installDirFromConfig === '') {
|
||||
let rootFolderName: string = '.sqlops';
|
||||
if (platform === Runtime.Windows_64 || platform === Runtime.Windows_86) {
|
||||
rootFolderName = 'sqlops';
|
||||
}
|
||||
installDirFromConfig = path.join(this.getLocalUserFolderPath(platform), `/${rootFolderName}/${this._extensionConstants.installFolderName}/{#version#}/{#platform#}`);
|
||||
}
|
||||
let basePath: string;
|
||||
if (path.isAbsolute(installDirFromConfig)) {
|
||||
basePath = installDirFromConfig;
|
||||
} else if (this._fromBuild) {
|
||||
basePath = path.join(__dirname, '../../../extensions/' + extensionConfigSectionName + '/' + installDirFromConfig);
|
||||
} else {
|
||||
// The path from config is relative to the out folder
|
||||
basePath = path.join(__dirname, '../../../../' + installDirFromConfig);
|
||||
}
|
||||
return basePath;
|
||||
}
|
||||
|
||||
private getGetDownloadUrl(fileName: string): string {
|
||||
let baseDownloadUrl = this._config.getDownloadUrl();
|
||||
let version = this._config.getPackageVersion();
|
||||
baseDownloadUrl = baseDownloadUrl.replace('{#version#}', version);
|
||||
baseDownloadUrl = baseDownloadUrl.replace('{#fileName#}', fileName);
|
||||
return baseDownloadUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the service and decompress it in the install folder.
|
||||
*/
|
||||
public installService(platform: Runtime): Promise<boolean> {
|
||||
const proxy = <string>this._config.getWorkspaceConfig('http.proxy');
|
||||
const strictSSL = this._config.getWorkspaceConfig('http.proxyStrictSSL', true);
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const fileName = this.getDownloadFileName(platform);
|
||||
const installDirectory = this.getInstallDirectory(platform, this._extensionConstants.extensionConfigSectionName);
|
||||
|
||||
this._logger.appendLine(`${this._extensionConstants.serviceInstallingTo} ${installDirectory}.`);
|
||||
const urlString = this.getGetDownloadUrl(fileName);
|
||||
|
||||
this._logger.appendLine(`${Constants.serviceDownloading} ${urlString}`);
|
||||
let pkg: IPackage = {
|
||||
installPath: installDirectory,
|
||||
url: urlString,
|
||||
tmpFile: undefined
|
||||
};
|
||||
this.createTempFile(pkg).then(tmpResult => {
|
||||
pkg.tmpFile = tmpResult;
|
||||
|
||||
this._httpClient.downloadFile(pkg.url, pkg, this._logger, this._statusView, proxy, strictSSL).then(_ => {
|
||||
|
||||
this._logger.logDebug(`Downloaded to ${pkg.tmpFile.name}...`);
|
||||
this._logger.appendLine(' Done!');
|
||||
this.install(pkg).then(result => {
|
||||
resolve(true);
|
||||
}).catch(installError => {
|
||||
reject(installError);
|
||||
});
|
||||
}).catch(downloadError => {
|
||||
this._logger.appendLine(`[ERROR] ${downloadError}`);
|
||||
reject(downloadError);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createTempFile(pkg: IPackage): Promise<tmp.SynchronousResult> {
|
||||
return new Promise<tmp.SynchronousResult>((resolve, reject) => {
|
||||
tmp.file({ prefix: 'package-' }, (err, path, fd, cleanupCallback) => {
|
||||
if (err) {
|
||||
return reject(new PackageError('Error from tmp.file', pkg, err));
|
||||
}
|
||||
|
||||
resolve(<tmp.SynchronousResult>{ name: path, fd: fd, removeCallback: cleanupCallback });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private install(pkg: IPackage): Promise<void> {
|
||||
this._logger.appendLine('Installing ...');
|
||||
this._statusView.installingService();
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this._decompressProvider.decompress(pkg, this._logger).then(_ => {
|
||||
this._statusView.serviceInstalled();
|
||||
resolve();
|
||||
}).catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Runtime, PlatformInformation } from '../models/platform';
|
||||
import Config from '../configurations/config';
|
||||
import ServiceDownloadProvider from './serviceDownloadProvider';
|
||||
import DecompressProvider from './decompressProvider';
|
||||
import HttpClient from './httpClient';
|
||||
import ServerProvider from './server';
|
||||
import { IStatusView } from './interfaces';
|
||||
import { ILogger } from '../models/interfaces';
|
||||
import { IExtensionConstants } from '../models/contracts/contracts';
|
||||
|
||||
class StubStatusView implements IStatusView {
|
||||
installingService(): void {
|
||||
console.log('...');
|
||||
}
|
||||
serviceInstalled(): void {
|
||||
console.log('Service installed');
|
||||
}
|
||||
serviceInstallationFailed(): void {
|
||||
console.log('Service installation failed');
|
||||
}
|
||||
updateServiceDownloadingProgress(downloadPercentage: number): void {
|
||||
if (downloadPercentage === 100) {
|
||||
process.stdout.write('100%');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StubLogger implements ILogger {
|
||||
logDebug(message: string): void {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
increaseIndent(): void {
|
||||
console.log('increaseIndent');
|
||||
}
|
||||
|
||||
decreaseIndent(): void {
|
||||
console.log('decreaseIndent');
|
||||
}
|
||||
|
||||
append(message?: string): void {
|
||||
process.stdout.write(message);
|
||||
}
|
||||
appendLine(message?: string): void {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
export class ServiceInstaller {
|
||||
private _config = undefined;
|
||||
private _logger = new StubLogger();
|
||||
private _statusView = new StubStatusView();
|
||||
private _httpClient = new HttpClient();
|
||||
private _decompressProvider = new DecompressProvider();
|
||||
private _downloadProvider = undefined;
|
||||
private _serverProvider = undefined;
|
||||
private _extensionConstants = undefined;
|
||||
|
||||
constructor(extensionConstants: IExtensionConstants, path?: string) {
|
||||
this._extensionConstants = extensionConstants;
|
||||
this._config = new Config(extensionConstants.extensionConfigSectionName, path, true);
|
||||
this._downloadProvider = new ServiceDownloadProvider(this._config, this._logger, this._statusView, this._httpClient, this._decompressProvider, extensionConstants, true);
|
||||
this._serverProvider = new ServerProvider(this._downloadProvider, this._config, this._statusView, extensionConstants.extensionConfigSectionName);
|
||||
}
|
||||
/*
|
||||
* Installs the service for the given platform if it's not already installed.
|
||||
*/
|
||||
public installService(): Promise<String> {
|
||||
return PlatformInformation.getCurrent(this._extensionConstants.getRuntimeId, this._extensionConstants.extensionName).then(platformInfo => {
|
||||
if (platformInfo.isValidRuntime()) {
|
||||
return this._serverProvider.getOrDownloadServer(platformInfo.runtimeId);
|
||||
} else {
|
||||
throw new Error('unsupported runtime');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the install folder path for given platform.
|
||||
*/
|
||||
public getServiceInstallDirectory(runtime: Runtime): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (runtime === undefined) {
|
||||
PlatformInformation.getCurrent(this._extensionConstants.getRuntimeId, this._extensionConstants.extensionName).then(platformInfo => {
|
||||
if (platformInfo.isValidRuntime()) {
|
||||
resolve(this._downloadProvider.getInstallDirectory(platformInfo.runtimeId));
|
||||
} else {
|
||||
reject('unsupported runtime');
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve(this._downloadProvider.getInstallDirectory(runtime));
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the path to the root folder of service install location.
|
||||
*/
|
||||
public getServiceInstallDirectoryRoot(runtime: Runtime): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (runtime === undefined) {
|
||||
PlatformInformation.getCurrent(this._extensionConstants.getRuntimeId, this._extensionConstants.extensionName).then(platformInfo => {
|
||||
if (platformInfo.isValidRuntime()) {
|
||||
let directoryPath: string = this._downloadProvider.getInstallDirectoryRoot(platformInfo, this._extensionConstants.extensionName);
|
||||
directoryPath = directoryPath.replace('\\{#version#}', '');
|
||||
directoryPath = directoryPath.replace('\\{#platform#}', '');
|
||||
directoryPath = directoryPath.replace('/{#platform#}', '');
|
||||
directoryPath = directoryPath.replace('/{#version#}', '');
|
||||
resolve(directoryPath);
|
||||
} else {
|
||||
reject('unsupported runtime');
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve(this._downloadProvider.getInstallDirectory(runtime));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import vscode = require('vscode');
|
||||
|
||||
export default class ServiceStatus implements vscode.Disposable {
|
||||
|
||||
private _progressTimerId: NodeJS.Timer;
|
||||
|
||||
private _statusBarItem: vscode.StatusBarItem = undefined;
|
||||
|
||||
private durationStatusInMs: number = 1500;
|
||||
|
||||
// These need localization
|
||||
private _serviceStartingMessage: string = `Starting ${this._serviceName}`;
|
||||
private _serviceStartedMessage: string = `${this._serviceName} started`;
|
||||
|
||||
constructor(private _serviceName: string) {
|
||||
this._statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
}
|
||||
|
||||
public showServiceLoading(): Promise<void> {
|
||||
return this === undefined ?
|
||||
Promise.resolve() :
|
||||
Promise.resolve(this.updateStatusView(this._serviceStartingMessage, true));
|
||||
}
|
||||
|
||||
public showServiceLoaded(): Promise<void> {
|
||||
return this === undefined ?
|
||||
Promise.resolve() :
|
||||
Promise.resolve(this.updateStatusView(this._serviceStartedMessage, false, this.durationStatusInMs));
|
||||
}
|
||||
|
||||
//TODO: This can be merged with the serverStatus code
|
||||
private showProgress(statusText: string): void {
|
||||
let index: number = 0;
|
||||
let progressTicks: string[] = ['.', '..', '...', '....'];
|
||||
|
||||
this._progressTimerId = setInterval(() => {
|
||||
index = (index + 1) % progressTicks.length;
|
||||
let progressTick = progressTicks[index];
|
||||
if (this._statusBarItem.text !== this._serviceStartedMessage) {
|
||||
this._statusBarItem.text = statusText + ' ' + progressTick;
|
||||
this._statusBarItem.show();
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
private updateStatusView(message: string, showAsProgress: boolean = false, disposeAfter: number = -1): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (showAsProgress) {
|
||||
this.showProgress(message);
|
||||
}
|
||||
else {
|
||||
this._statusBarItem.text = message;
|
||||
this._statusBarItem.show();
|
||||
if (this._progressTimerId !== undefined) {
|
||||
clearInterval(this._progressTimerId);
|
||||
}
|
||||
}
|
||||
if (disposeAfter !== -1) {
|
||||
setInterval(() => {
|
||||
this._statusBarItem.hide();
|
||||
}, disposeAfter);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this._progressTimerId !== undefined) {
|
||||
clearInterval(this._progressTimerId);
|
||||
}
|
||||
this._statusBarItem.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
export * from './controllers/vscodeWrapper';
|
||||
export * from './models/constants';
|
||||
export * from './models/utils';
|
||||
|
||||
export { SqlToolsServiceClient } from './languageservice/serviceClient';
|
||||
export { IExtensionConstants } from './models/contracts/contracts';
|
||||
export { ILanguageClientHelper } from './models/contracts/languageService';
|
||||
export { Runtime, PlatformInformation } from './models/platform';
|
||||
export { Telemetry } from './models/telemetry';
|
||||
export { LinuxDistribution } from './models/platform';
|
||||
export { ServiceInstaller } from './languageservice/serviceInstallerUtil';
|
||||
@@ -1,25 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export namespace Constants {
|
||||
//constants
|
||||
export const configLogDebugInfo: string = 'logDebugInfo';
|
||||
export const serviceNotCompatibleError: string = 'Client is not compatible with the service layer';
|
||||
export const serviceDownloading: string = 'Downloading';
|
||||
export const serviceInstalling: string = 'Installing';
|
||||
export const unsupportedPlatformErrorMessage: string = 'This platform is unsupported and application services may not function correctly';
|
||||
export const serviceConfigKey = 'service';
|
||||
export const executableFilesConfigKey = 'executableFiles';
|
||||
export const versionConfigKey = 'version';
|
||||
export const downloadUrlConfigKey = 'downloadUrl';
|
||||
export const installDirConfigKey = 'installDir';
|
||||
export const serviceCrashButton = 'View Known Issues';
|
||||
export const neverShowAgain = 'Do not show again';
|
||||
export const ignorePlatformWarning = 'ignorePlatformWarning';
|
||||
export const usingDefaultPlatformMessage = 'Unknown platform detected, defaulting to Linux_x64 platform';
|
||||
export const serverConnectionMetadata = 'serverConnectionMetadata';
|
||||
export const extensionDeactivated: string = 'de-activated.';
|
||||
export const extensionActivated: string = 'activated.';
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import { Runtime, LinuxDistribution } from '../platform';
|
||||
|
||||
// --------------------------------- < Version Request > -------------------------------------------------
|
||||
|
||||
// Version request message callback declaration
|
||||
export namespace VersionRequest {
|
||||
export const type = new RequestType<void, VersionResult, void, void>('version');
|
||||
}
|
||||
|
||||
// Version response format
|
||||
export type VersionResult = string;
|
||||
|
||||
// ------------------------------- </ Version Request > --------------------------------------------------
|
||||
|
||||
// Constants interface for each extension
|
||||
export interface IExtensionConstants {
|
||||
// TODO: Fill in interface
|
||||
|
||||
// Definitely dependent on the extension
|
||||
extensionName: string;
|
||||
invalidServiceFilePath: string;
|
||||
serviceName: string;
|
||||
extensionConfigSectionName: string;
|
||||
serviceCompatibleVersion: string;
|
||||
outputChannelName: string;
|
||||
languageId: string;
|
||||
serviceInstallingTo: string;
|
||||
serviceInitializing: string;
|
||||
serviceInstalled: string;
|
||||
serviceLoadingFailed: string;
|
||||
serviceInstallationFailed: string;
|
||||
serviceInitializingOutputChannelName: string;
|
||||
commandsNotAvailableWhileInstallingTheService: string;
|
||||
providerId: string;
|
||||
serviceCrashMessage: string;
|
||||
serviceCrashLink: string;
|
||||
installFolderName: string;
|
||||
telemetryExtensionName: string;
|
||||
|
||||
getRuntimeId(platform: string, architecture: string, distribution: LinuxDistribution): Runtime;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { NotificationType, ServerOptions, RequestType, RPCMessageType, ClientCapabilities, ServerCapabilities } from 'vscode-languageclient';
|
||||
import { ITelemetryEventProperties, ITelemetryEventMeasures } from '../telemetry';
|
||||
import { Runtime } from '../platform';
|
||||
import { SqlOpsFeature, SqlOpsDataClient } from 'dataprotocol-client';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||
|
||||
/**
|
||||
* Event sent when the language service send a telemetry event
|
||||
*/
|
||||
export namespace TelemetryNotification {
|
||||
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event parameters
|
||||
*/
|
||||
export class TelemetryParams {
|
||||
public params: {
|
||||
eventName: string;
|
||||
properties: ITelemetryEventProperties;
|
||||
measures: ITelemetryEventMeasures;
|
||||
};
|
||||
}
|
||||
|
||||
// ------------------------------- </ Telemetry Sent Event > ----------------------------------
|
||||
|
||||
// ------------------------------- < Status Event > ------------------------------------
|
||||
|
||||
/**
|
||||
* Event sent when the language service send a status change event
|
||||
*/
|
||||
export namespace StatusChangedNotification {
|
||||
export const type = new NotificationType<StatusChangeParams, void>('textDocument/statusChanged');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event parameters
|
||||
*/
|
||||
export class StatusChangeParams {
|
||||
/**
|
||||
* URI identifying the text document
|
||||
*/
|
||||
public ownerUri: string;
|
||||
|
||||
/**
|
||||
* The new status of the document
|
||||
*/
|
||||
public status: string;
|
||||
}
|
||||
|
||||
// ------------------------------- </ Status Sent Event > ----------------------------------
|
||||
|
||||
export interface ILanguageClientHelper {
|
||||
createServerOptions(servicePath: string, runtimeId?: Runtime): ServerOptions;
|
||||
}
|
||||
|
||||
// Job Management types
|
||||
export interface AgentJobsParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface AgentJobsResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
jobs: sqlops.AgentJobInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
jobs: sqlops.AgentJobHistoryInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobActionParams {
|
||||
ownerUri: string;
|
||||
jobName: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface AgentJobActionResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export namespace AgentJobsRequest {
|
||||
export const type = new RequestType<AgentJobsParams, AgentJobsResult, void, void>('agent/jobs');
|
||||
}
|
||||
|
||||
export namespace AgentJobHistoryRequest {
|
||||
export const type = new RequestType<AgentJobHistoryParams, AgentJobHistoryResult, void, void>('agent/jobhistory');
|
||||
}
|
||||
|
||||
|
||||
export namespace AgentJobActionRequest {
|
||||
export const type = new RequestType<AgentJobActionParams, AgentJobActionResult, void, void>('agent/jobaction');
|
||||
}
|
||||
|
||||
export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
AgentJobsRequest.type,
|
||||
AgentJobHistoryRequest.type,
|
||||
AgentJobActionRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, AgentServicesFeature.messagesTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
// this isn't explicitly necessary
|
||||
// ensure(ensure(capabilities, 'connection')!, 'agentServices')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let getJobs = (ownerUri: string): Thenable<sqlops.AgentJobsResult> => {
|
||||
let params: AgentJobsParams = { ownerUri: ownerUri, jobId: null };
|
||||
return client.sendRequest(AgentJobsRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobsRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let getJobHistory = (connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> => {
|
||||
let params: AgentJobHistoryParams = { ownerUri: connectionUri, jobId: jobID };
|
||||
|
||||
return client.sendRequest(AgentJobHistoryRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobHistoryRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let jobAction = (connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult> => {
|
||||
let params: AgentJobActionParams = { ownerUri: connectionUri, jobName: jobName, action: action };
|
||||
return client.sendRequest(AgentJobActionRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobActionRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
getJobHistory,
|
||||
jobAction
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as os from 'os';
|
||||
import { ILogger } from './interfaces';
|
||||
import { Utils } from './utils';
|
||||
import { IExtensionConstants } from './contracts/contracts';
|
||||
|
||||
/*
|
||||
* Logger class handles logging messages using the Util functions.
|
||||
*/
|
||||
export class Logger implements ILogger {
|
||||
private _writer: (message: string) => void;
|
||||
private _prefix: string;
|
||||
private _extensionConstants: IExtensionConstants;
|
||||
|
||||
private _indentLevel: number = 0;
|
||||
private _indentSize: number = 4;
|
||||
private _atLineStart: boolean = false;
|
||||
|
||||
constructor(writer: (message: string) => void, extensionConstants: IExtensionConstants, prefix?: string) {
|
||||
this._writer = writer;
|
||||
this._prefix = prefix;
|
||||
this._extensionConstants = extensionConstants;
|
||||
}
|
||||
|
||||
public logDebug(message: string): void {
|
||||
Utils.logDebug(message, this._extensionConstants.extensionConfigSectionName);
|
||||
}
|
||||
|
||||
private _appendCore(message: string): void {
|
||||
if (this._atLineStart) {
|
||||
if (this._indentLevel > 0) {
|
||||
const indent = ' '.repeat(this._indentLevel * this._indentSize);
|
||||
this._writer(indent);
|
||||
}
|
||||
|
||||
if (this._prefix) {
|
||||
this._writer(`[${this._prefix}] `);
|
||||
}
|
||||
|
||||
this._atLineStart = false;
|
||||
}
|
||||
|
||||
this._writer(message);
|
||||
}
|
||||
|
||||
public increaseIndent(): void {
|
||||
this._indentLevel += 1;
|
||||
}
|
||||
|
||||
public decreaseIndent(): void {
|
||||
if (this._indentLevel > 0) {
|
||||
this._indentLevel -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
public append(message?: string): void {
|
||||
message = message || '';
|
||||
this._appendCore(message);
|
||||
}
|
||||
|
||||
public appendLine(message?: string): void {
|
||||
message = message || '';
|
||||
this._appendCore(message + os.EOL);
|
||||
this._atLineStart = true;
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as child_process from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
|
||||
const unknown = 'unknown';
|
||||
|
||||
export enum Runtime {
|
||||
UnknownRuntime = <any>'Unknown',
|
||||
UnknownVersion = <any>'Unknown',
|
||||
Windows_86 = <any>'Windows_86',
|
||||
Windows_64 = <any>'Windows_64',
|
||||
OSX = <any>'OSX',
|
||||
CentOS_7 = <any>'CentOS_7',
|
||||
Debian_8 = <any>'Debian_8',
|
||||
Fedora_23 = <any>'Fedora_23',
|
||||
OpenSUSE_13_2 = <any>'OpenSUSE_13_2',
|
||||
SLES_12_2 = <any>'SLES_12_2',
|
||||
RHEL_7 = <any>'RHEL_7',
|
||||
Ubuntu_14 = <any>'Ubuntu_14',
|
||||
Ubuntu_16 = <any>'Ubuntu_16',
|
||||
Linux_64 = <any>'Linux_64',
|
||||
Linux_86 = <any>'Linux-86'
|
||||
}
|
||||
|
||||
export function getRuntimeDisplayName(runtime: Runtime): string {
|
||||
switch (runtime) {
|
||||
case Runtime.Windows_64:
|
||||
return 'Windows';
|
||||
case Runtime.Windows_86:
|
||||
return 'Windows';
|
||||
case Runtime.OSX:
|
||||
return 'OSX';
|
||||
case Runtime.CentOS_7:
|
||||
return 'Linux';
|
||||
case Runtime.Debian_8:
|
||||
return 'Linux';
|
||||
case Runtime.Fedora_23:
|
||||
return 'Linux';
|
||||
case Runtime.OpenSUSE_13_2:
|
||||
return 'Linux';
|
||||
case Runtime.SLES_12_2:
|
||||
return 'Linux';
|
||||
case Runtime.RHEL_7:
|
||||
return 'Linux';
|
||||
case Runtime.Ubuntu_14:
|
||||
return 'Linux';
|
||||
case Runtime.Ubuntu_16:
|
||||
return 'Linux';
|
||||
case Runtime.Linux_64:
|
||||
return 'Linux';
|
||||
case Runtime.Linux_86:
|
||||
return 'Linux';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export class PlatformInformation {
|
||||
public runtimeId: Runtime;
|
||||
|
||||
public constructor(
|
||||
public platform: string,
|
||||
public architecture: string,
|
||||
public distribution: LinuxDistribution = undefined,
|
||||
public getRuntimeId: (platform: string, architecture: string, distribution: LinuxDistribution) => Runtime) {
|
||||
try {
|
||||
this.runtimeId = this.getRuntimeId(platform, architecture, distribution);
|
||||
} catch (err) {
|
||||
this.runtimeId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public isWindows(): boolean {
|
||||
return this.platform === 'win32';
|
||||
}
|
||||
|
||||
public isMacOS(): boolean {
|
||||
return this.platform === 'darwin';
|
||||
}
|
||||
|
||||
public isLinux(): boolean {
|
||||
return this.platform === 'linux';
|
||||
}
|
||||
|
||||
public isValidRuntime(): boolean {
|
||||
return this.runtimeId !== undefined && this.runtimeId !== Runtime.UnknownRuntime && this.runtimeId !== Runtime.UnknownVersion;
|
||||
}
|
||||
|
||||
public getRuntimeDisplayName(): string {
|
||||
return getRuntimeDisplayName(this.runtimeId);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
let result = this.platform;
|
||||
|
||||
if (this.architecture) {
|
||||
if (result) {
|
||||
result += ', ';
|
||||
}
|
||||
|
||||
result += this.architecture;
|
||||
}
|
||||
|
||||
if (this.distribution) {
|
||||
if (result) {
|
||||
result += ', ';
|
||||
}
|
||||
|
||||
result += this.distribution.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getCurrent(getRuntimeId: (platform: string, architecture: string, distribution: LinuxDistribution) => Runtime,
|
||||
extensionName: string): Promise<any> {
|
||||
let platform = os.platform();
|
||||
let architecturePromise: Promise<string>;
|
||||
let distributionPromise: Promise<LinuxDistribution>;
|
||||
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
architecturePromise = PlatformInformation.getWindowsArchitecture();
|
||||
distributionPromise = Promise.resolve(undefined);
|
||||
break;
|
||||
|
||||
case 'darwin':
|
||||
let osVersion = os.release();
|
||||
if (parseFloat(osVersion) < 16.0 && extensionName === 'mssql') {
|
||||
return Promise.reject('The current version of macOS is not supported. Only macOS Sierra and above (>= 10.12) are supported.');
|
||||
}
|
||||
architecturePromise = PlatformInformation.getUnixArchitecture();
|
||||
distributionPromise = Promise.resolve(undefined);
|
||||
break;
|
||||
|
||||
case 'linux':
|
||||
architecturePromise = PlatformInformation.getUnixArchitecture();
|
||||
distributionPromise = LinuxDistribution.getCurrent();
|
||||
break;
|
||||
|
||||
default:
|
||||
return Promise.reject(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
|
||||
return architecturePromise.then(arch => {
|
||||
return distributionPromise.then(distro => {
|
||||
return new PlatformInformation(platform, arch, distro, getRuntimeId);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static getWindowsArchitecture(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
// try to get the architecture from WMIC
|
||||
PlatformInformation.getWindowsArchitectureWmic().then(architecture => {
|
||||
if (architecture && architecture !== unknown) {
|
||||
resolve(architecture);
|
||||
} else {
|
||||
// sometimes WMIC isn't available on the path so then try to parse the envvar
|
||||
PlatformInformation.getWindowsArchitectureEnv().then(architecture => {
|
||||
resolve(architecture);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static getWindowsArchitectureWmic(): Promise<string> {
|
||||
return this.execChildProcess('wmic os get osarchitecture')
|
||||
.then(architecture => {
|
||||
if (architecture) {
|
||||
let archArray: string[] = architecture.split(os.EOL);
|
||||
if (archArray.length >= 2) {
|
||||
let arch = archArray[1].trim();
|
||||
|
||||
// Note: This string can be localized. So, we'll just check to see if it contains 32 or 64.
|
||||
if (arch.indexOf('64') >= 0) {
|
||||
return 'x86_64';
|
||||
} else if (arch.indexOf('32') >= 0) {
|
||||
return 'x86';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unknown;
|
||||
}).catch((error) => {
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
private static getWindowsArchitectureEnv(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (process.env.PROCESSOR_ARCHITECTURE === 'x86' && process.env.PROCESSOR_ARCHITEW6432 === undefined) {
|
||||
resolve('x86');
|
||||
}
|
||||
else {
|
||||
resolve('x86_64');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static getUnixArchitecture(): Promise<string> {
|
||||
return this.execChildProcess('uname -m')
|
||||
.then(architecture => {
|
||||
if (architecture) {
|
||||
return architecture.trim();
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private static execChildProcess(process: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
child_process.exec(process, { maxBuffer: 500 * 1024 }, (error: Error, stdout: string, stderr: string) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr && stderr.length > 0) {
|
||||
reject(new Error(stderr));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There is no standard way on Linux to find the distribution name and version.
|
||||
* Recently, systemd has pushed to standardize the os-release file. This has
|
||||
* seen adoption in "recent" versions of all major distributions.
|
||||
* https://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
*/
|
||||
export class LinuxDistribution {
|
||||
public constructor(
|
||||
public name: string,
|
||||
public version: string,
|
||||
public idLike?: string[]) { }
|
||||
|
||||
public static getCurrent(): Promise<LinuxDistribution> {
|
||||
// Try /etc/os-release and fallback to /usr/lib/os-release per the synopsis
|
||||
// at https://www.freedesktop.org/software/systemd/man/os-release.html.
|
||||
return LinuxDistribution.fromFilePath('/etc/os-release')
|
||||
.catch(() => LinuxDistribution.fromFilePath('/usr/lib/os-release'))
|
||||
.catch(() => Promise.resolve(new LinuxDistribution(unknown, unknown)));
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `name=${this.name}, version=${this.version}`;
|
||||
}
|
||||
|
||||
private static fromFilePath(filePath: string): Promise<LinuxDistribution> {
|
||||
return new Promise<LinuxDistribution>((resolve, reject) => {
|
||||
fs.readFile(filePath, 'utf8', (error, data) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(LinuxDistribution.fromReleaseInfo(data));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static fromReleaseInfo(releaseInfo: string, eol: string = os.EOL): LinuxDistribution {
|
||||
let name = unknown;
|
||||
let version = unknown;
|
||||
let idLike: string[] = undefined;
|
||||
|
||||
const lines = releaseInfo.split(eol);
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
|
||||
let equalsIndex = line.indexOf('=');
|
||||
if (equalsIndex >= 0) {
|
||||
let key = line.substring(0, equalsIndex);
|
||||
let value = line.substring(equalsIndex + 1);
|
||||
|
||||
// Strip quotes if necessary
|
||||
if (value.length > 1 && value.startsWith('"') && value.endsWith('"')) {
|
||||
value = value.substring(1, value.length - 1);
|
||||
} else if (value.length > 1 && value.startsWith('\'') && value.endsWith('\'')) {
|
||||
value = value.substring(1, value.length - 1);
|
||||
}
|
||||
|
||||
if (key === 'ID') {
|
||||
name = value;
|
||||
} else if (key === 'VERSION_ID') {
|
||||
version = value;
|
||||
} else if (key === 'ID_LIKE') {
|
||||
idLike = value.split(' ');
|
||||
}
|
||||
|
||||
if (name !== unknown && version !== unknown && idLike !== undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LinuxDistribution(name, version, idLike);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
import vscode = require('vscode');
|
||||
import { Constants } from './constants';
|
||||
import { ExtensionContext } from 'vscode';
|
||||
var path = require('path');
|
||||
|
||||
export namespace Utils {
|
||||
// INTERFACES /////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Interface for package.json information
|
||||
export interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Get information from the extension's package.json file
|
||||
export function getPackageInfo(context: ExtensionContext): IPackageInfo {
|
||||
let extensionPackage = require(context.asAbsolutePath('./package.json'));
|
||||
if (extensionPackage) {
|
||||
return {
|
||||
name: extensionPackage.name,
|
||||
version: extensionPackage.version,
|
||||
aiKey: extensionPackage.aiKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a new GUID
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
// Generate a unique, deterministic ID for the current user of the extension
|
||||
export function generateUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
try {
|
||||
let interfaces = os.networkInterfaces();
|
||||
let mac;
|
||||
for (let key of Object.keys(interfaces)) {
|
||||
let item = interfaces[key][0];
|
||||
if (!item.internal) {
|
||||
mac = item.mac;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mac) {
|
||||
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(generateGuid());
|
||||
}
|
||||
} catch (err) {
|
||||
resolve(generateGuid()); // fallback
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Retrieve the URI for the currently open file if there is one; otherwise return the empty string
|
||||
export function getActiveTextEditorUri(): string {
|
||||
if (typeof vscode.window.activeTextEditor !== 'undefined' &&
|
||||
typeof vscode.window.activeTextEditor.document !== 'undefined') {
|
||||
return vscode.window.activeTextEditor.document.uri.toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Helper to log debug messages
|
||||
export function logDebug(msg: any, extensionConfigSectionName: string): void {
|
||||
let config = vscode.workspace.getConfiguration(extensionConfigSectionName);
|
||||
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||
if (logDebugInfo === true) {
|
||||
let currentTime = new Date().toLocaleTimeString();
|
||||
let outputMsg = '[' + currentTime + ']: ' + msg ? msg.toString() : '';
|
||||
console.log(outputMsg);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to show an error message
|
||||
export function showErrorMsg(msg: string, extensionName: string): void {
|
||||
vscode.window.showErrorMessage(extensionName + ': ' + msg);
|
||||
}
|
||||
|
||||
export function isEmpty(str: any): boolean {
|
||||
return (!str || '' === str);
|
||||
}
|
||||
|
||||
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
|
||||
// work for now because the extension is running in different process.
|
||||
export function getAppDataPath() {
|
||||
var platform = process.platform;
|
||||
switch (platform) {
|
||||
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
|
||||
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
default: throw new Error('Platform not supported');
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultLogLocation() {
|
||||
return path.join(getAppDataPath(), 'sqlops');
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "../lib",
|
||||
"lib": [
|
||||
"es6", "es2015.promise"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import vscode = require('vscode');
|
||||
import { Utils } from '../models/utils';
|
||||
|
||||
// Status bar element for each file in the editor
|
||||
class FileStatusBar {
|
||||
// Item for the connection status
|
||||
public statusConnection: vscode.StatusBarItem;
|
||||
|
||||
// Item for the query status
|
||||
public statusQuery: vscode.StatusBarItem;
|
||||
|
||||
// Item for language service status
|
||||
public statusLanguageService: vscode.StatusBarItem;
|
||||
|
||||
// Timer used for displaying a progress indicator on queries
|
||||
public progressTimerId: NodeJS.Timer;
|
||||
|
||||
public currentLanguageServiceStatus: string;
|
||||
}
|
||||
|
||||
export default class StatusView implements vscode.Disposable {
|
||||
private _statusBars: { [fileUri: string]: FileStatusBar };
|
||||
private _lastShownStatusBar: FileStatusBar;
|
||||
|
||||
constructor() {
|
||||
this._statusBars = {};
|
||||
vscode.window.onDidChangeActiveTextEditor((params) => this.onDidChangeActiveTextEditor(params));
|
||||
vscode.workspace.onDidCloseTextDocument((params) => this.onDidCloseTextDocument(params));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
for (let bar in this._statusBars) {
|
||||
if (this._statusBars.hasOwnProperty(bar)) {
|
||||
this._statusBars[bar].statusConnection.dispose();
|
||||
this._statusBars[bar].statusQuery.dispose();
|
||||
this._statusBars[bar].statusLanguageService.dispose();
|
||||
clearInterval(this._statusBars[bar].progressTimerId);
|
||||
delete this._statusBars[bar];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create status bar item if needed
|
||||
private createStatusBar(fileUri: string): void {
|
||||
let bar = new FileStatusBar();
|
||||
bar.statusConnection = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
bar.statusQuery = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
bar.statusLanguageService = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||
this._statusBars[fileUri] = bar;
|
||||
}
|
||||
|
||||
private destroyStatusBar(fileUri: string): void {
|
||||
let bar = this._statusBars[fileUri];
|
||||
if (bar) {
|
||||
if (bar.statusConnection) {
|
||||
bar.statusConnection.dispose();
|
||||
}
|
||||
if (bar.statusQuery) {
|
||||
bar.statusQuery.dispose();
|
||||
}
|
||||
if (bar.statusLanguageService) {
|
||||
bar.statusLanguageService.dispose();
|
||||
}
|
||||
if (bar.progressTimerId) {
|
||||
clearInterval(bar.progressTimerId);
|
||||
}
|
||||
|
||||
delete this._statusBars[fileUri];
|
||||
}
|
||||
}
|
||||
|
||||
private getStatusBar(fileUri: string): FileStatusBar {
|
||||
if (!(fileUri in this._statusBars)) {
|
||||
// Create it if it does not exist
|
||||
this.createStatusBar(fileUri);
|
||||
}
|
||||
|
||||
let bar = this._statusBars[fileUri];
|
||||
if (bar.progressTimerId) {
|
||||
clearInterval(bar.progressTimerId);
|
||||
}
|
||||
return bar;
|
||||
}
|
||||
|
||||
public languageServiceStatusChanged(fileUri: string, status: string): void {
|
||||
let bar = this.getStatusBar(fileUri);
|
||||
bar.currentLanguageServiceStatus = status;
|
||||
this.updateStatusMessage(status,
|
||||
() => { return bar.currentLanguageServiceStatus; }, (message) => {
|
||||
bar.statusLanguageService.text = message;
|
||||
this.showStatusBarItem(fileUri, bar.statusLanguageService);
|
||||
});
|
||||
}
|
||||
|
||||
public updateStatusMessage(
|
||||
newStatus: string,
|
||||
getCurrentStatus: () => string,
|
||||
updateMessage: (message: string) => void): void {
|
||||
}
|
||||
|
||||
private hideLastShownStatusBar(): void {
|
||||
if (typeof this._lastShownStatusBar !== 'undefined') {
|
||||
this._lastShownStatusBar.statusConnection.hide();
|
||||
this._lastShownStatusBar.statusQuery.hide();
|
||||
this._lastShownStatusBar.statusLanguageService.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeActiveTextEditor(editor: vscode.TextEditor): void {
|
||||
// Hide the most recently shown status bar
|
||||
this.hideLastShownStatusBar();
|
||||
|
||||
// Change the status bar to match the open file
|
||||
if (typeof editor !== 'undefined') {
|
||||
const fileUri = editor.document.uri.toString();
|
||||
const bar = this._statusBars[fileUri];
|
||||
if (bar) {
|
||||
this.showStatusBarItem(fileUri, bar.statusConnection);
|
||||
this.showStatusBarItem(fileUri, bar.statusLanguageService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidCloseTextDocument(doc: vscode.TextDocument): void {
|
||||
// Remove the status bar associated with the document
|
||||
this.destroyStatusBar(doc.uri.toString());
|
||||
}
|
||||
|
||||
private showStatusBarItem(fileUri: string, statusBarItem: vscode.StatusBarItem): void {
|
||||
let currentOpenFile = Utils.getActiveTextEditorUri();
|
||||
|
||||
// Only show the status bar if it matches the currently open file and is not empty
|
||||
if (fileUri === currentOpenFile && !Utils.isEmpty(statusBarItem.text)) {
|
||||
statusBarItem.show();
|
||||
if (fileUri in this._statusBars) {
|
||||
this._lastShownStatusBar = this._statusBars[fileUri];
|
||||
}
|
||||
} else {
|
||||
statusBarItem.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,8 +19,8 @@
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "extension.clearTokenCache",
|
||||
"title": "%extension.clearTokenCache%",
|
||||
"command": "accounts.clearTokenCache",
|
||||
"title": "%accounts.clearTokenCache%",
|
||||
"category": "Azure Accounts"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extension.clearTokenCache": "Clear Azure Account Token Cache",
|
||||
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
||||
|
||||
@@ -20,7 +20,7 @@ let localize = nls.loadMessageBundle();
|
||||
|
||||
export class AzureAccountProviderService implements vscode.Disposable {
|
||||
// CONSTANTS ///////////////////////////////////////////////////////////////
|
||||
private static CommandClearTokenCache = 'extension.clearTokenCache';
|
||||
private static CommandClearTokenCache = 'accounts.azure.clearTokenCache';
|
||||
private static ConfigurationSection = 'accounts.azure';
|
||||
private static CredentialNamespace = 'azureAccountProviderCredentials';
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,147 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Constants } from '../models/constants';
|
||||
import { Serialization } from '../serialize/serialization';
|
||||
import { CredentialStore } from '../credentialstore/credentialstore';
|
||||
import { AzureResourceProvider } from '../resourceProvider/resourceProvider';
|
||||
import { IExtensionConstants, Telemetry, Constants as SharedConstants, SqlToolsServiceClient, VscodeWrapper, Utils, PlatformInformation } from 'extensions-modules';
|
||||
import { SqlOpsDataClient } from 'dataprotocol-client';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export default class MainController implements vscode.Disposable {
|
||||
private _context: vscode.ExtensionContext;
|
||||
private _vscodeWrapper: VscodeWrapper;
|
||||
private _initialized: boolean = false;
|
||||
private _serialization: Serialization;
|
||||
private _credentialStore: CredentialStore;
|
||||
private static _extensionConstants: IExtensionConstants = new Constants();
|
||||
private _client: SqlToolsServiceClient;
|
||||
/**
|
||||
* The main controller constructor
|
||||
* @constructor
|
||||
*/
|
||||
constructor(context: vscode.ExtensionContext,
|
||||
vscodeWrapper?: VscodeWrapper) {
|
||||
this._context = context;
|
||||
this._vscodeWrapper = vscodeWrapper || new VscodeWrapper(MainController._extensionConstants);
|
||||
SqlToolsServiceClient.constants = MainController._extensionConstants;
|
||||
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
|
||||
this._credentialStore = new CredentialStore(this._client);
|
||||
this._serialization = new Serialization(this._client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the controller
|
||||
*/
|
||||
dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
Utils.logDebug(SharedConstants.extensionDeactivated, MainController._extensionConstants.extensionConfigSectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the extension
|
||||
*/
|
||||
public activate(): Promise<boolean> {
|
||||
return this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flag indicating if the extension is initialized
|
||||
*/
|
||||
public isInitialized(): boolean {
|
||||
return this._initialized;
|
||||
}
|
||||
|
||||
private createClient(executableFiles: string[]): Promise<SqlOpsDataClient> {
|
||||
return PlatformInformation.getCurrent(SqlToolsServiceClient.constants.getRuntimeId, SqlToolsServiceClient.constants.extensionName).then(platformInfo => {
|
||||
return SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json')).createClient(this._context, platformInfo.runtimeId, undefined, executableFiles);
|
||||
});
|
||||
}
|
||||
|
||||
private createCredentialClient(): Promise<SqlOpsDataClient> {
|
||||
return this.createClient(['MicrosoftSqlToolsCredentials.exe', 'MicrosoftSqlToolsCredentials']);
|
||||
}
|
||||
|
||||
private createResourceProviderClient(): Promise<SqlOpsDataClient> {
|
||||
return this.createClient(['SqlToolsResourceProviderService.exe', 'SqlToolsResourceProviderService']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the extension
|
||||
*/
|
||||
public initialize(): Promise<boolean> {
|
||||
|
||||
// initialize language service client
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const self = this;
|
||||
SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json')).initialize(self._context).then(serverResult => {
|
||||
|
||||
// Initialize telemetry
|
||||
Telemetry.initialize(self._context, new Constants());
|
||||
|
||||
// telemetry for activation
|
||||
Telemetry.sendTelemetryEvent('ExtensionActivated', {},
|
||||
{ serviceInstalled: serverResult.installedBeforeInitializing ? 1 : 0 }
|
||||
);
|
||||
|
||||
self.createResourceProviderClient().then(rpClient => {
|
||||
let resourceProvider = new AzureResourceProvider(self._client, rpClient);
|
||||
sqlops.resources.registerResourceProvider({
|
||||
displayName: 'Azure SQL Resource Provider', // TODO Localize
|
||||
id: 'Microsoft.Azure.SQL.ResourceProvider',
|
||||
settings: {
|
||||
|
||||
}
|
||||
}, resourceProvider);
|
||||
Utils.logDebug('resourceProvider registered', MainController._extensionConstants.extensionConfigSectionName);
|
||||
}, error => {
|
||||
Utils.logDebug('Cannot find ResourceProvider executables. error: ' + error, MainController._extensionConstants.extensionConfigSectionName);
|
||||
});
|
||||
|
||||
self.createCredentialClient().then(credentialClient => {
|
||||
self._credentialStore.languageClient = credentialClient;
|
||||
credentialClient.onReady().then(() => {
|
||||
let credentialProvider: sqlops.CredentialProvider = {
|
||||
handle: 0,
|
||||
saveCredential(credentialId: string, password: string): Thenable<boolean> {
|
||||
return self._credentialStore.saveCredential(credentialId, password);
|
||||
},
|
||||
readCredential(credentialId: string): Thenable<sqlops.Credential> {
|
||||
return self._credentialStore.readCredential(credentialId);
|
||||
},
|
||||
deleteCredential(credentialId: string): Thenable<boolean> {
|
||||
return self._credentialStore.deleteCredential(credentialId);
|
||||
}
|
||||
};
|
||||
sqlops.credentials.registerProvider(credentialProvider);
|
||||
Utils.logDebug('credentialProvider registered', MainController._extensionConstants.extensionConfigSectionName);
|
||||
});
|
||||
}, error => {
|
||||
Utils.logDebug('Cannot find credentials executables. error: ' + error, MainController._extensionConstants.extensionConfigSectionName);
|
||||
});
|
||||
|
||||
Utils.logDebug(SharedConstants.extensionActivated, MainController._extensionConstants.extensionConfigSectionName);
|
||||
self._initialized = true;
|
||||
resolve(true);
|
||||
}).catch(err => {
|
||||
Telemetry.sendTelemetryEventForException(err, 'initialize', MainController._extensionConstants.extensionConfigSectionName);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as Contracts from '../models/contracts';
|
||||
import { ICredentialStore } from './icredentialstore';
|
||||
import { SqlToolsServiceClient, Utils } from 'extensions-modules';
|
||||
import { SqlOpsDataClient } from 'dataprotocol-client';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Implements a credential storage for Windows, Mac (darwin), or Linux.
|
||||
*
|
||||
* Allows a single credential to be stored per service (that is, one username per service);
|
||||
*/
|
||||
export class CredentialStore implements ICredentialStore {
|
||||
|
||||
public languageClient: SqlOpsDataClient;
|
||||
|
||||
constructor(private _client?: SqlToolsServiceClient) {
|
||||
if (!this._client) {
|
||||
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a credential saved in the credential store
|
||||
*
|
||||
* @param {string} credentialId the ID uniquely identifying this credential
|
||||
* @returns {Promise<Credential>} Promise that resolved to the credential, or undefined if not found
|
||||
*/
|
||||
public readCredential(credentialId: string): Promise<Contracts.Credential> {
|
||||
Utils.logDebug(this.languageClient, 'MainController._extensionConstants');
|
||||
let self = this;
|
||||
let cred: Contracts.Credential = new Contracts.Credential();
|
||||
cred.credentialId = credentialId;
|
||||
return new Promise<Contracts.Credential>((resolve, reject) => {
|
||||
self._client
|
||||
.sendRequest(Contracts.ReadCredentialRequest.type, cred, this.languageClient)
|
||||
.then(returnedCred => {
|
||||
resolve(<Contracts.Credential>returnedCred);
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
public saveCredential(credentialId: string, password: any): Promise<boolean> {
|
||||
let self = this;
|
||||
let cred: Contracts.Credential = new Contracts.Credential();
|
||||
cred.credentialId = credentialId;
|
||||
cred.password = password;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
self._client
|
||||
.sendRequest(Contracts.SaveCredentialRequest.type, cred, this.languageClient)
|
||||
.then(status => {
|
||||
resolve(<boolean>status);
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
public deleteCredential(credentialId: string): Promise<boolean> {
|
||||
let self = this;
|
||||
let cred: Contracts.Credential = new Contracts.Credential();
|
||||
cred.credentialId = credentialId;
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
self._client
|
||||
.sendRequest(Contracts.DeleteCredentialRequest.type, cred, this.languageClient)
|
||||
.then(status => {
|
||||
resolve(<boolean>status);
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
|
||||
// This code is originally from https://github.com/microsoft/vsts-vscode
|
||||
// License: https://github.com/Microsoft/vsts-vscode/blob/master/LICENSE.txt
|
||||
|
||||
import { Credential } from '../models/contracts';
|
||||
|
||||
/**
|
||||
* A credential store that securely stores sensitive information in a platform-specific manner
|
||||
*
|
||||
* @export
|
||||
* @interface ICredentialStore
|
||||
*/
|
||||
export interface ICredentialStore {
|
||||
readCredential(credentialId: string): Promise<Credential>;
|
||||
saveCredential(credentialId: string, password: any): Promise<boolean>;
|
||||
deleteCredential(credentialId: string): Promise<boolean>;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import vscode = require('vscode');
|
||||
import MainController from './controllers/mainController';
|
||||
import ContextProvider from './contextProvider';
|
||||
|
||||
export let controller: MainController;
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
controller = new MainController(context);
|
||||
let contextProvider = new ContextProvider();
|
||||
context.subscriptions.push(controller);
|
||||
context.subscriptions.push(contextProvider);
|
||||
controller.activate();
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate(): void {
|
||||
if (controller) {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionConstants } from 'extensions-modules/lib/models/contracts/contracts';
|
||||
import { Runtime, LinuxDistribution } from 'extensions-modules/lib/models/platform';
|
||||
|
||||
// constants
|
||||
export class Constants implements IExtensionConstants {
|
||||
public readonly languageId = 'sql';
|
||||
public readonly extensionName = 'mssql';
|
||||
public readonly extensionConfigSectionName = 'mssql';
|
||||
public readonly outputChannelName = 'MSSQL';
|
||||
public readonly providerId = 'MSSQL';
|
||||
public readonly installFolderName = 'sqltoolsservice';
|
||||
public readonly telemetryExtensionName = 'carbon-mssql';
|
||||
|
||||
// localizable strings
|
||||
public readonly serviceCompatibleVersion = '1.0.0';
|
||||
public readonly serviceInstallingTo = 'Installing SQL tools service to';
|
||||
public readonly serviceInitializing = 'Initializing SQL tools service for the mssql extension.';
|
||||
public readonly commandsNotAvailableWhileInstallingTheService = 'Note: mssql commands will be available after installing the service.';
|
||||
public readonly serviceInstalled = 'Sql Tools Service installed';
|
||||
public readonly serviceInstallationFailed = 'Failed to install Sql Tools Service';
|
||||
public readonly serviceLoadingFailed = 'Failed to load Sql Tools Service';
|
||||
public readonly invalidServiceFilePath = 'Invalid file path for Sql Tools Service';
|
||||
public readonly serviceName = 'SQLToolsService';
|
||||
public readonly serviceInitializingOutputChannelName = 'SqlToolsService Initialization';
|
||||
public readonly serviceCrashMessage = 'SQL Tools Service component exited unexpectedly. Please restart SQL Operations Studio.';
|
||||
public readonly serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
|
||||
|
||||
/**
|
||||
* Returns a supported .NET Core Runtime ID (RID) for the current platform. The list of Runtime IDs
|
||||
* is available at https://github.com/dotnet/corefx/tree/master/pkg/Microsoft.NETCore.Platforms.
|
||||
*/
|
||||
public getRuntimeId(platform: string, architecture: string, distribution: LinuxDistribution): Runtime {
|
||||
switch (platform) {
|
||||
case 'win32':
|
||||
switch (architecture) {
|
||||
case 'x86': return Runtime.Windows_86;
|
||||
case 'x86_64': return Runtime.Windows_64;
|
||||
default:
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported Windows architecture: ${architecture}`);
|
||||
|
||||
case 'darwin':
|
||||
if (architecture === 'x86_64') {
|
||||
// Note: We return the El Capitan RID for Sierra
|
||||
return Runtime.OSX;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported macOS architecture: ${architecture}`);
|
||||
|
||||
case 'linux':
|
||||
if (architecture === 'x86_64') {
|
||||
|
||||
// First try the distribution name
|
||||
let runtimeId = Constants.getRuntimeIdHelper(distribution.name, distribution.version);
|
||||
|
||||
// If the distribution isn't one that we understand, but the 'ID_LIKE' field has something that we understand, use that
|
||||
//
|
||||
// NOTE: 'ID_LIKE' doesn't specify the version of the 'like' OS. So we will use the 'VERSION_ID' value. This will restrict
|
||||
// how useful ID_LIKE will be since it requires the version numbers to match up, but it is the best we can do.
|
||||
if (runtimeId === Runtime.UnknownRuntime && distribution.idLike && distribution.idLike.length > 0) {
|
||||
for (let id of distribution.idLike) {
|
||||
runtimeId = Constants.getRuntimeIdHelper(id, distribution.version);
|
||||
if (runtimeId !== Runtime.UnknownRuntime) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runtimeId !== Runtime.UnknownRuntime && runtimeId !== Runtime.UnknownVersion) {
|
||||
return runtimeId;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, this is not a Linux distro or architecture that we currently support.
|
||||
throw new Error(`Unsupported Linux distro: ${distribution.name}, ${distribution.version}, ${architecture}`);
|
||||
default:
|
||||
// If we got here, we've ended up with a platform we don't support like 'freebsd' or 'sunos'.
|
||||
// Chances are, VS Code doesn't support these platforms either.
|
||||
throw Error('Unsupported platform ' + platform);
|
||||
}
|
||||
}
|
||||
|
||||
private static getRuntimeIdHelper(distributionName: string, distributionVersion: string): Runtime {
|
||||
switch (distributionName) {
|
||||
case 'ubuntu':
|
||||
if (distributionVersion.startsWith('14')) {
|
||||
// This also works for Linux Mint
|
||||
return Runtime.Ubuntu_14;
|
||||
} else if (distributionVersion.startsWith('16')) {
|
||||
return Runtime.Ubuntu_16;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'elementary':
|
||||
case 'elementary OS':
|
||||
if (distributionVersion.startsWith('0.3')) {
|
||||
// Elementary OS 0.3 Freya is binary compatible with Ubuntu 14.04
|
||||
return Runtime.Ubuntu_14;
|
||||
} else if (distributionVersion.startsWith('0.4')) {
|
||||
// Elementary OS 0.4 Loki is binary compatible with Ubuntu 16.04
|
||||
return Runtime.Ubuntu_16;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'linuxmint':
|
||||
if (distributionVersion.startsWith('18')) {
|
||||
// Linux Mint 18 is binary compatible with Ubuntu 16.04
|
||||
return Runtime.Ubuntu_16;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'centos':
|
||||
case 'ol':
|
||||
// Oracle Linux is binary compatible with CentOS
|
||||
return Runtime.CentOS_7;
|
||||
case 'fedora':
|
||||
return Runtime.Fedora_23;
|
||||
case 'opensuse':
|
||||
return Runtime.OpenSUSE_13_2;
|
||||
case 'sles':
|
||||
return Runtime.SLES_12_2;
|
||||
case 'rhel':
|
||||
return Runtime.RHEL_7;
|
||||
case 'debian':
|
||||
return Runtime.Debian_8;
|
||||
case 'galliumos':
|
||||
if (distributionVersion.startsWith('2.0')) {
|
||||
return Runtime.Ubuntu_16;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Runtime.UnknownRuntime;
|
||||
}
|
||||
|
||||
return Runtime.UnknownVersion;
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
// DEV-NOTE: Still finalizing what we'll need as part of this interface
|
||||
/**
|
||||
* Contains necessary information for serializing and saving results
|
||||
* @param {string} saveFormat the format / type that the results will be saved in
|
||||
* @param {string} savePath path the results will be saved to
|
||||
* @param {string} results either a subset or all of the results we wish to save to savePath
|
||||
* @param {boolean} appendToFile Whether we should append or overwrite the file in savePath
|
||||
*/
|
||||
export class SaveResultsInfo {
|
||||
constructor(public saveFormat: string, public savePath: string, public results: string,
|
||||
public appendToFile: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace SaveAsRequest {
|
||||
export const type = new RequestType<SaveResultsInfo, sqlops.SaveResultRequestResult, void, void>('query/saveAs');
|
||||
}
|
||||
|
||||
// --------------------------------- < Read Credential Request > -------------------------------------------------
|
||||
|
||||
// Read Credential request message callback declaration
|
||||
export namespace ReadCredentialRequest {
|
||||
export const type = new RequestType<Credential, Credential, void, void>('credential/read');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters to initialize a connection to a database
|
||||
*/
|
||||
export class Credential {
|
||||
/**
|
||||
* Unique ID identifying the credential
|
||||
*/
|
||||
public credentialId: string;
|
||||
|
||||
/**
|
||||
* password
|
||||
*/
|
||||
public password: string;
|
||||
}
|
||||
|
||||
// --------------------------------- </ Read Credential Request > -------------------------------------------------
|
||||
|
||||
// --------------------------------- < Save Credential Request > -------------------------------------------------
|
||||
|
||||
// Save Credential request message callback declaration
|
||||
export namespace SaveCredentialRequest {
|
||||
export const type = new RequestType<Credential, boolean, void, void>('credential/save');
|
||||
}
|
||||
// --------------------------------- </ Save Credential Request > -------------------------------------------------
|
||||
|
||||
|
||||
// --------------------------------- < Delete Credential Request > -------------------------------------------------
|
||||
|
||||
// Delete Credential request message callback declaration
|
||||
export namespace DeleteCredentialRequest {
|
||||
export const type = new RequestType<Credential, boolean, void, void>('credential/delete');
|
||||
}
|
||||
// --------------------------------- </ Delete Credential Request > -------------------------------------------------
|
||||
|
||||
// ------------------------------- < Resource Events > ------------------------------------
|
||||
export namespace CreateFirewallRuleRequest {
|
||||
export const type = new RequestType<CreateFirewallRuleParams, CreateFirewallRuleResponse, void, void>('resource/createFirewallRule');
|
||||
}
|
||||
|
||||
export namespace HandleFirewallRuleRequest {
|
||||
export const type = new RequestType<HandleFirewallRuleParams, HandleFirewallRuleResponse, void, void>('resource/handleFirewallRule');
|
||||
}
|
||||
|
||||
// Firewall rule interfaces
|
||||
export interface CreateFirewallRuleParams {
|
||||
account: sqlops.Account;
|
||||
serverName: string;
|
||||
startIpAddress: string;
|
||||
endIpAddress: string;
|
||||
securityTokenMappings: {};
|
||||
}
|
||||
|
||||
export interface CreateFirewallRuleResponse {
|
||||
result: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface HandleFirewallRuleParams {
|
||||
errorCode: number;
|
||||
errorMessage: string;
|
||||
connectionTypeId: string;
|
||||
}
|
||||
|
||||
export interface HandleFirewallRuleResponse {
|
||||
result: boolean;
|
||||
ipAddress: string;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as Contracts from '../models/contracts';
|
||||
import { SqlToolsServiceClient } from 'extensions-modules';
|
||||
import { SqlOpsDataClient } from 'dataprotocol-client';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as path from 'path';
|
||||
|
||||
|
||||
/**
|
||||
* Implements a credential storage for Windows, Mac (darwin), or Linux.
|
||||
*
|
||||
* Allows a single credential to be stored per service (that is, one username per service);
|
||||
*/
|
||||
export class AzureResourceProvider implements sqlops.ResourceProvider {
|
||||
|
||||
public languageClient: SqlOpsDataClient;
|
||||
|
||||
constructor(private _client?: SqlToolsServiceClient, langClient?: SqlOpsDataClient) {
|
||||
if (!this._client) {
|
||||
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
|
||||
}
|
||||
this.languageClient = langClient;
|
||||
}
|
||||
|
||||
public createFirewallRule(account: sqlops.Account, firewallruleInfo: sqlops.FirewallRuleInfo): Thenable<sqlops.CreateFirewallRuleResponse> {
|
||||
let self = this;
|
||||
return new Promise<sqlops.CreateFirewallRuleResponse>((resolve, reject) => {
|
||||
self._client.
|
||||
sendRequest(Contracts.CreateFirewallRuleRequest.type, self.asCreateFirewallRuleParams(account, firewallruleInfo), self.languageClient)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
public handleFirewallRule(errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<sqlops.HandleFirewallRuleResponse> {
|
||||
let self = this;
|
||||
return new Promise<sqlops.HandleFirewallRuleResponse>((resolve, reject) => {
|
||||
let params: Contracts.HandleFirewallRuleParams = { errorCode: errorCode, errorMessage: errorMessage, connectionTypeId: connectionTypeId };
|
||||
|
||||
self._client.
|
||||
sendRequest(Contracts.HandleFirewallRuleRequest.type, params, self.languageClient)
|
||||
.then(response => {
|
||||
resolve(response);
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
private asCreateFirewallRuleParams(account: sqlops.Account, params: sqlops.FirewallRuleInfo): Contracts.CreateFirewallRuleParams {
|
||||
return {
|
||||
account: account,
|
||||
serverName: params.serverName,
|
||||
startIpAddress: params.startIpAddress,
|
||||
endIpAddress: params.endIpAddress,
|
||||
securityTokenMappings: params.securityTokenMappings
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as Contracts from '../models/contracts';
|
||||
import { ISerialization } from './iserialization';
|
||||
import { SqlToolsServiceClient } from 'extensions-modules';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { SqlOpsDataClient } from 'dataprotocol-client';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Implements serializer for query results
|
||||
*/
|
||||
export class Serialization implements ISerialization {
|
||||
|
||||
constructor(private _client?: SqlToolsServiceClient, private _languageClient?: SqlOpsDataClient) {
|
||||
if (!this._client) {
|
||||
this._client = SqlToolsServiceClient.getInstance(path.join(__dirname, '../config.json'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves results as a specified path
|
||||
*
|
||||
* @param {string} credentialId the ID uniquely identifying this credential
|
||||
* @returns {Promise<ISaveResultsInfo>} Promise that resolved to the credential, or undefined if not found
|
||||
*/
|
||||
public saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Promise<sqlops.SaveResultRequestResult> {
|
||||
let self = this;
|
||||
let resultsInfo: Contracts.SaveResultsInfo = new Contracts.SaveResultsInfo(saveFormat, savePath, results, appendToFile);
|
||||
return new Promise<sqlops.SaveResultRequestResult>((resolve, reject) => {
|
||||
self._client
|
||||
.sendRequest(Contracts.SaveAsRequest.type, resultsInfo, this._languageClient)
|
||||
.then(result => {
|
||||
resolve(<sqlops.SaveResultRequestResult>result);
|
||||
}, err => reject(err));
|
||||
});
|
||||
}
|
||||
}
|
||||
4
extensions/mssql/npm-shrinkwrap.json
generated
4
extensions/mssql/npm-shrinkwrap.json
generated
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "mssql",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
@@ -3,14 +3,13 @@
|
||||
"version": "0.1.0",
|
||||
"publisher": "Microsoft",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"engines": {
|
||||
"vscode": "0.10.x",
|
||||
"sqlops": "feature/agent1"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./client/out/main",
|
||||
"engines": {
|
||||
"vscode": "*"
|
||||
},
|
||||
"main": "./out/main",
|
||||
"extensionDependencies": [
|
||||
"vscode.sql"
|
||||
],
|
||||
@@ -659,9 +658,16 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"extensions-modules": "file:../../extensions-modules"
|
||||
"crypto": "^1.0.1",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.5",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
"resolutions": {
|
||||
"vscode-jsonrpc": "3.5.0",
|
||||
"vscode-languageclient": "3.5.0",
|
||||
"vscode-languageserver-protocol": "3.5.0",
|
||||
"vscode-languageserver-types": "3.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
19
extensions/mssql/src/config.json
Normal file
19
extensions/mssql/src/config.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.10",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.0.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.0.zip",
|
||||
"OSX": "osx-x64-netcoreapp2.0.tar.gz",
|
||||
"CentOS_7": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Debian_8": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Fedora_23": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"OpenSUSE_13_2": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"RHEL_7": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"SLES_12_2": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Ubuntu_14": "rhel-x64-netcoreapp2.0.tar.gz",
|
||||
"Ubuntu_16": "rhel-x64-netcoreapp2.0.tar.gz"
|
||||
},
|
||||
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
|
||||
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]
|
||||
}
|
||||
11
extensions/mssql/src/constants.ts
Normal file
11
extensions/mssql/src/constants.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const serviceName = 'SqlToolsService';
|
||||
export const providerId = 'MSSQL';
|
||||
export const serviceCrashMessage = 'SQL Tools Service component exited unexpectedly. Please restart SQL Operations Studio.';
|
||||
export const serviceCrashButton = 'View Known Issues';
|
||||
export const serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
|
||||
80
extensions/mssql/src/contracts.ts
Normal file
80
extensions/mssql/src/contracts.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { NotificationType, RequestType } from 'vscode-languageclient';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { ITelemetryEventProperties, ITelemetryEventMeasures } from './telemetry';
|
||||
|
||||
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||
|
||||
/**
|
||||
* Event sent when the language service send a telemetry event
|
||||
*/
|
||||
export namespace TelemetryNotification {
|
||||
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event parameters
|
||||
*/
|
||||
export class TelemetryParams {
|
||||
public params: {
|
||||
eventName: string;
|
||||
properties: ITelemetryEventProperties;
|
||||
measures: ITelemetryEventMeasures;
|
||||
};
|
||||
}
|
||||
|
||||
// ------------------------------- </ Telemetry Sent Event > ----------------------------------
|
||||
|
||||
|
||||
// Job Management types
|
||||
export interface AgentJobsParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface AgentJobsResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
jobs: sqlops.AgentJobInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
jobs: sqlops.AgentJobHistoryInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobActionParams {
|
||||
ownerUri: string;
|
||||
jobName: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface AgentJobActionResult {
|
||||
succeeded: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export namespace AgentJobsRequest {
|
||||
export const type = new RequestType<AgentJobsParams, AgentJobsResult, void, void>('agent/jobs');
|
||||
}
|
||||
|
||||
export namespace AgentJobHistoryRequest {
|
||||
export const type = new RequestType<AgentJobHistoryParams, AgentJobHistoryResult, void, void>('agent/jobhistory');
|
||||
}
|
||||
|
||||
|
||||
export namespace AgentJobActionRequest {
|
||||
export const type = new RequestType<AgentJobActionParams, AgentJobActionResult, void, void>('agent/jobaction');
|
||||
}
|
||||
@@ -4,10 +4,5 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface ILogger {
|
||||
logDebug(message: string): void;
|
||||
increaseIndent(): void;
|
||||
decreaseIndent(): void;
|
||||
append(message?: string): void;
|
||||
appendLine(message?: string): void;
|
||||
}
|
||||
export const serviceName = 'SerilizationProvider';
|
||||
export const providerId = 'serilizationProvider';
|
||||
34
extensions/mssql/src/credentialstore/contracts.ts
Normal file
34
extensions/mssql/src/credentialstore/contracts.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import { Credential } from 'sqlops';
|
||||
|
||||
// --------------------------------- < Read Credential Request > -------------------------------------------------
|
||||
|
||||
// Read Credential request message callback declaration
|
||||
export namespace ReadCredentialRequest {
|
||||
export const type = new RequestType<Credential, Credential, void, void>('credential/read');
|
||||
}
|
||||
|
||||
// --------------------------------- </ Read Credential Request > -------------------------------------------------
|
||||
|
||||
// --------------------------------- < Save Credential Request > -------------------------------------------------
|
||||
|
||||
// Save Credential request message callback declaration
|
||||
export namespace SaveCredentialRequest {
|
||||
export const type = new RequestType<Credential, boolean, void, void>('credential/save');
|
||||
}
|
||||
// --------------------------------- </ Save Credential Request > -------------------------------------------------
|
||||
|
||||
|
||||
// --------------------------------- < Delete Credential Request > -------------------------------------------------
|
||||
|
||||
// Delete Credential request message callback declaration
|
||||
export namespace DeleteCredentialRequest {
|
||||
export const type = new RequestType<Credential, boolean, void, void>('credential/delete');
|
||||
}
|
||||
// --------------------------------- </ Delete Credential Request > -------------------------------------------------
|
||||
110
extensions/mssql/src/credentialstore/credentialstore.ts
Normal file
110
extensions/mssql/src/credentialstore/credentialstore.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, ClientOptions, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import * as path from 'path';
|
||||
import { IConfig, ServerProvider } from 'service-downloader';
|
||||
import { ServerOptions, RPCMessageType, ClientCapabilities, ServerCapabilities, TransportKind } from 'vscode-languageclient';
|
||||
import { Disposable } from 'vscode';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import * as Contracts from './contracts';
|
||||
import * as Constants from './constants';
|
||||
import * as Utils from '../utils';
|
||||
|
||||
class CredentialsFeature extends SqlOpsFeature<any> {
|
||||
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
Contracts.DeleteCredentialRequest.type,
|
||||
Contracts.SaveCredentialRequest.type,
|
||||
Contracts.ReadCredentialRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, CredentialsFeature.messagesTypes);
|
||||
}
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
Utils.ensure(Utils.ensure(capabilities, 'credentials')!, 'credentials')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: any): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let readCredential = (credentialId: string): Thenable<sqlops.Credential> => {
|
||||
return client.sendRequest(Contracts.ReadCredentialRequest.type, { credentialId });
|
||||
};
|
||||
|
||||
let saveCredential = (credentialId: string, password: string): Thenable<boolean> => {
|
||||
return client.sendRequest(Contracts.SaveCredentialRequest.type, { credentialId, password });
|
||||
};
|
||||
|
||||
let deleteCredential = (credentialId: string): Thenable<boolean> => {
|
||||
return client.sendRequest(Contracts.DeleteCredentialRequest.type, { credentialId });
|
||||
};
|
||||
|
||||
return sqlops.credentials.registerProvider({
|
||||
deleteCredential,
|
||||
readCredential,
|
||||
saveCredential,
|
||||
handle: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a credential storage for Windows, Mac (darwin), or Linux.
|
||||
*
|
||||
* Allows a single credential to be stored per service (that is, one username per service);
|
||||
*/
|
||||
export class CredentialStore {
|
||||
private _client: SqlOpsDataClient;
|
||||
private _config: IConfig;
|
||||
|
||||
constructor(baseConfig: IConfig) {
|
||||
if (baseConfig) {
|
||||
this._config = JSON.parse(JSON.stringify(baseConfig));
|
||||
this._config.executableFiles = ['MicrosoftSqlToolsCredentials.exe', 'MicrosoftSqlToolsCredentials'];
|
||||
}
|
||||
}
|
||||
|
||||
public start() {
|
||||
let serverdownloader = new ServerProvider(this._config);
|
||||
let clientOptions: ClientOptions = {
|
||||
providerId: Constants.providerId,
|
||||
features: [CredentialsFeature]
|
||||
};
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
this._client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
this._client.start();
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this._client) {
|
||||
this._client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), 'mssql');
|
||||
launchArgs.push(logFileLocation);
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
}
|
||||
99
extensions/mssql/src/features.ts
Normal file
99
extensions/mssql/src/features.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import { ClientCapabilities, StaticFeature, RPCMessageType, ServerCapabilities } from 'vscode-languageclient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { Disposable } from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import { Telemetry } from './telemetry';
|
||||
import * as Utils from './utils';
|
||||
import { TelemetryNotification, AgentJobsRequest, AgentJobActionRequest, AgentJobHistoryRequest, AgentJobsParams, AgentJobHistoryParams, AgentJobActionParams } from './contracts';
|
||||
|
||||
export class TelemetryFeature implements StaticFeature {
|
||||
|
||||
constructor(private _client: SqlOpsDataClient) { }
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
Utils.ensure(capabilities, 'telemetry')!.telemetry = true;
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this._client.onNotification(TelemetryNotification.type, e => {
|
||||
Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
AgentJobsRequest.type,
|
||||
AgentJobHistoryRequest.type,
|
||||
AgentJobActionRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, AgentServicesFeature.messagesTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
// this isn't explicitly necessary
|
||||
// ensure(ensure(capabilities, 'connection')!, 'agentServices')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let getJobs = (ownerUri: string): Thenable<sqlops.AgentJobsResult> => {
|
||||
let params: AgentJobsParams = { ownerUri: ownerUri, jobId: null };
|
||||
return client.sendRequest(AgentJobsRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobsRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let getJobHistory = (connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> => {
|
||||
let params: AgentJobHistoryParams = { ownerUri: connectionUri, jobId: jobID };
|
||||
|
||||
return client.sendRequest(AgentJobHistoryRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobHistoryRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let jobAction = (connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult> => {
|
||||
let params: AgentJobActionParams = { ownerUri: connectionUri, jobName: jobName, action: action };
|
||||
return client.sendRequest(AgentJobActionRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(AgentJobActionRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
getJobHistory,
|
||||
jobAction
|
||||
});
|
||||
}
|
||||
}
|
||||
136
extensions/mssql/src/main.ts
Normal file
136
extensions/mssql/src/main.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||
import { ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
|
||||
import * as Constants from './constants';
|
||||
import ContextProvider from './contextProvider';
|
||||
import { CredentialStore } from './credentialstore/credentialstore';
|
||||
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
||||
import * as Utils from './utils';
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import { TelemetryFeature } from './features';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||
const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
// lets make sure we support this platform first
|
||||
let supported = await Utils.verifyPlatform();
|
||||
|
||||
if (!supported) {
|
||||
vscode.window.showErrorMessage('Unsupported platform');
|
||||
return;
|
||||
}
|
||||
|
||||
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
||||
config.installDirectory = path.join(__dirname, config.installDirectory);
|
||||
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||
|
||||
const credentialsStore = new CredentialStore(config);
|
||||
const resourceProvider = new AzureResourceProvider(config);
|
||||
let languageClient: SqlOpsDataClient;
|
||||
|
||||
const serverdownloader = new ServerProvider(config);
|
||||
|
||||
serverdownloader.eventEmitter.onAny(generateHandleServerProviderEvent());
|
||||
|
||||
let clientOptions: ClientOptions = {
|
||||
providerId: Constants.providerId,
|
||||
errorHandler: new LanguageClientErrorHandler(),
|
||||
features: [
|
||||
// we only want to add new features
|
||||
...SqlOpsDataClient.defaultFeatures,
|
||||
TelemetryFeature
|
||||
]
|
||||
};
|
||||
|
||||
const installationStart = Date.now();
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
const installationComplete = Date.now();
|
||||
let serverOptions = generateServerOptions(e);
|
||||
languageClient = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
const processStart = Date.now();
|
||||
languageClient.onReady().then(() => {
|
||||
const processEnd = Date.now();
|
||||
statusView.text = 'Service Started';
|
||||
setTimeout(() => {
|
||||
statusView.hide();
|
||||
}, 1500);
|
||||
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||
installationTime: String(installationComplete - installationStart),
|
||||
processStartupTime: String(processEnd - processStart),
|
||||
totalTime: String(processEnd - installationStart),
|
||||
beginningTimestamp: String(installationStart)
|
||||
});
|
||||
});
|
||||
statusView.show();
|
||||
statusView.text = 'Starting service';
|
||||
languageClient.start();
|
||||
credentialsStore.start();
|
||||
resourceProvider.start();
|
||||
}, e => {
|
||||
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||
vscode.window.showErrorMessage('Failed to start Sql tools service');
|
||||
});
|
||||
|
||||
let contextProvider = new ContextProvider();
|
||||
context.subscriptions.push(contextProvider);
|
||||
context.subscriptions.push(credentialsStore);
|
||||
context.subscriptions.push(resourceProvider);
|
||||
context.subscriptions.push({ dispose: () => languageClient.stop() });
|
||||
}
|
||||
|
||||
function generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), 'mssql');
|
||||
launchArgs.push(logFileLocation);
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
|
||||
function generateHandleServerProviderEvent() {
|
||||
let dots = 0;
|
||||
return (e: string, ...args: any[]) => {
|
||||
outputChannel.show();
|
||||
statusView.show();
|
||||
switch (e) {
|
||||
case Events.INSTALL_START:
|
||||
outputChannel.appendLine(`Installing ${Constants.serviceName} service to ${args[0]}`);
|
||||
statusView.text = 'Installing Service';
|
||||
break;
|
||||
case Events.INSTALL_END:
|
||||
outputChannel.appendLine('Installed');
|
||||
break;
|
||||
case Events.DOWNLOAD_START:
|
||||
outputChannel.appendLine(`Downloading ${args[0]}`);
|
||||
outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
|
||||
statusView.text = 'Downloading Service';
|
||||
break;
|
||||
case Events.DOWNLOAD_PROGRESS:
|
||||
let newDots = Math.ceil(args[0] / 5);
|
||||
if (newDots > dots) {
|
||||
outputChannel.append('.'.repeat(newDots - dots));
|
||||
dots = newDots;
|
||||
}
|
||||
break;
|
||||
case Events.DOWNLOAD_END:
|
||||
outputChannel.appendLine('Done!');
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate(): void {
|
||||
}
|
||||
@@ -4,14 +4,5 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
/**
|
||||
* Serializer for saving results into a different format
|
||||
*
|
||||
* @export
|
||||
* @interface ISerialization
|
||||
*/
|
||||
export interface ISerialization {
|
||||
saveAs(saveFormat: string, savePath: string, results: string, appendToFile: boolean): Promise<sqlops.SaveResultRequestResult>;
|
||||
}
|
||||
export const serviceName = 'AzureResourceProvider';
|
||||
export const providerId = 'azureresourceProvider';
|
||||
42
extensions/mssql/src/resourceprovider/contracts.ts
Normal file
42
extensions/mssql/src/resourceprovider/contracts.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
// ------------------------------- < Resource Events > ------------------------------------
|
||||
export namespace CreateFirewallRuleRequest {
|
||||
export const type = new RequestType<CreateFirewallRuleParams, CreateFirewallRuleResponse, void, void>('resource/createFirewallRule');
|
||||
}
|
||||
|
||||
export namespace HandleFirewallRuleRequest {
|
||||
export const type = new RequestType<HandleFirewallRuleParams, HandleFirewallRuleResponse, void, void>('resource/handleFirewallRule');
|
||||
}
|
||||
|
||||
// Firewall rule interfaces
|
||||
export interface CreateFirewallRuleParams {
|
||||
account: sqlops.Account;
|
||||
serverName: string;
|
||||
startIpAddress: string;
|
||||
endIpAddress: string;
|
||||
securityTokenMappings: {};
|
||||
}
|
||||
|
||||
export interface CreateFirewallRuleResponse {
|
||||
result: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface HandleFirewallRuleParams {
|
||||
errorCode: number;
|
||||
errorMessage: string;
|
||||
connectionTypeId: string;
|
||||
}
|
||||
|
||||
export interface HandleFirewallRuleResponse {
|
||||
result: boolean;
|
||||
ipAddress: string;
|
||||
}
|
||||
115
extensions/mssql/src/resourceprovider/resourceprovider.ts
Normal file
115
extensions/mssql/src/resourceprovider/resourceprovider.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import { IConfig, ServerProvider } from 'service-downloader';
|
||||
import { SqlOpsDataClient, SqlOpsFeature, ClientOptions } from 'dataprotocol-client';
|
||||
import { ServerCapabilities, ClientCapabilities, RPCMessageType, ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
import { CreateFirewallRuleRequest, HandleFirewallRuleRequest, CreateFirewallRuleParams, HandleFirewallRuleParams } from './contracts';
|
||||
import * as Constants from './constants';
|
||||
import * as Utils from '../utils';
|
||||
|
||||
class FireWallFeature extends SqlOpsFeature<any> {
|
||||
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
CreateFirewallRuleRequest.type,
|
||||
HandleFirewallRuleRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, FireWallFeature.messagesTypes);
|
||||
}
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
Utils.ensure(Utils.ensure(capabilities, 'firewall')!, 'firwall')!.dynamicRegistration = true;
|
||||
}
|
||||
|
||||
initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: any): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let createFirewallRule = (account: sqlops.Account, firewallruleInfo: sqlops.FirewallRuleInfo): Thenable<sqlops.CreateFirewallRuleResponse> => {
|
||||
return client.sendRequest(CreateFirewallRuleRequest.type, asCreateFirewallRuleParams(account, firewallruleInfo));
|
||||
};
|
||||
|
||||
let handleFirewallRule = (errorCode: number, errorMessage: string, connectionTypeId: string): Thenable<sqlops.HandleFirewallRuleResponse> => {
|
||||
let params: HandleFirewallRuleParams = { errorCode: errorCode, errorMessage: errorMessage, connectionTypeId: connectionTypeId };
|
||||
return client.sendRequest(HandleFirewallRuleRequest.type, params);
|
||||
};
|
||||
|
||||
return sqlops.resources.registerResourceProvider({
|
||||
displayName: 'Azure SQL Resource Provider', // TODO Localize
|
||||
id: 'Microsoft.Azure.SQL.ResourceProvider',
|
||||
settings: {
|
||||
|
||||
}
|
||||
}, {
|
||||
handleFirewallRule,
|
||||
createFirewallRule
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function asCreateFirewallRuleParams(account: sqlops.Account, params: sqlops.FirewallRuleInfo): CreateFirewallRuleParams {
|
||||
return {
|
||||
account: account,
|
||||
serverName: params.serverName,
|
||||
startIpAddress: params.startIpAddress,
|
||||
endIpAddress: params.endIpAddress,
|
||||
securityTokenMappings: params.securityTokenMappings
|
||||
};
|
||||
}
|
||||
|
||||
export class AzureResourceProvider {
|
||||
private _client: SqlOpsDataClient;
|
||||
private _config: IConfig;
|
||||
|
||||
constructor(baseConfig: IConfig) {
|
||||
if (baseConfig) {
|
||||
this._config = JSON.parse(JSON.stringify(baseConfig));
|
||||
this._config.executableFiles = ['SqlToolsResourceProviderService.exe', 'SqlToolsResourceProviderService'];
|
||||
}
|
||||
}
|
||||
|
||||
public start() {
|
||||
let serverdownloader = new ServerProvider(this._config);
|
||||
let clientOptions: ClientOptions = {
|
||||
providerId: Constants.providerId,
|
||||
features: [FireWallFeature]
|
||||
};
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
this._client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
this._client.start();
|
||||
});
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
if (this._client) {
|
||||
this._client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(Utils.getDefaultLogLocation(), 'mssql');
|
||||
launchArgs.push(logFileLocation);
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,16 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import vscode = require('vscode');
|
||||
import * as vscode from 'vscode';
|
||||
import * as opener from 'opener';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { Utils } from './utils';
|
||||
import { PlatformInformation, Runtime, LinuxDistribution } from './platform';
|
||||
import { IExtensionConstants } from './contracts/contracts';
|
||||
import { PlatformInformation } from 'service-downloader/out/platform';
|
||||
import { ErrorAction, ErrorHandler, Message, CloseAction } from 'vscode-languageclient';
|
||||
|
||||
import * as Utils from './utils';
|
||||
import * as Constants from './constants';
|
||||
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
export interface ITelemetryEventProperties {
|
||||
[key: string]: string;
|
||||
@@ -38,15 +43,6 @@ export class Telemetry {
|
||||
private static userId: string;
|
||||
private static platformInformation: PlatformInformation;
|
||||
private static disabled: boolean;
|
||||
private static _getRuntimeId: (platform: string, architecture: string, distribution: LinuxDistribution) => Runtime;
|
||||
|
||||
public static get getRuntimeId() {
|
||||
return this._getRuntimeId;
|
||||
}
|
||||
|
||||
public static set getRuntimeId(runtimeIdGetter: (platform: string, architecture: string, distribution: LinuxDistribution) => Runtime) {
|
||||
this._getRuntimeId = runtimeIdGetter;
|
||||
}
|
||||
|
||||
// Get the unique ID for the current user of the extension
|
||||
public static getUserId(): Promise<string> {
|
||||
@@ -69,7 +65,7 @@ export class Telemetry {
|
||||
return Promise.resolve(this.platformInformation);
|
||||
} else {
|
||||
return new Promise<PlatformInformation>(resolve => {
|
||||
PlatformInformation.getCurrent(this.getRuntimeId, 'telemetry').then(info => {
|
||||
PlatformInformation.getCurrent().then(info => {
|
||||
this.platformInformation = info;
|
||||
resolve(this.platformInformation);
|
||||
});
|
||||
@@ -77,8 +73,6 @@ export class Telemetry {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Disable telemetry reporting
|
||||
*/
|
||||
@@ -89,7 +83,7 @@ export class Telemetry {
|
||||
/**
|
||||
* Initialize the telemetry reporter for use.
|
||||
*/
|
||||
public static initialize(context: vscode.ExtensionContext, extensionConstants: IExtensionConstants): void {
|
||||
public static initialize(): void {
|
||||
if (typeof this.reporter === 'undefined') {
|
||||
// Check if the user has opted out of telemetry
|
||||
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
|
||||
@@ -97,8 +91,8 @@ export class Telemetry {
|
||||
return;
|
||||
}
|
||||
|
||||
let packageInfo = Utils.getPackageInfo(context);
|
||||
this.reporter = new TelemetryReporter(extensionConstants.telemetryExtensionName, packageInfo.version, packageInfo.aiKey);
|
||||
let packageInfo = Utils.getPackageInfo(packageJson);
|
||||
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,10 +114,10 @@ export class Telemetry {
|
||||
|
||||
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
|
||||
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
|
||||
Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
|
||||
// Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
|
||||
} catch (telemetryErr) {
|
||||
// If sending telemetry event fails ignore it so it won't break the extension
|
||||
Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
|
||||
// Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +132,7 @@ export class Telemetry {
|
||||
if (typeof this.disabled === 'undefined') {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
if (this.disabled || typeof (this.reporter) === 'undefined') {
|
||||
// Don't do anything if telemetry is disabled
|
||||
return;
|
||||
@@ -148,7 +143,7 @@ export class Telemetry {
|
||||
}
|
||||
|
||||
// Augment the properties structure with additional common properties before sending
|
||||
Promise.all([this.getUserId, this.getPlatformInformation]).then(() => {
|
||||
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||
properties['userId'] = this.userId;
|
||||
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||
@@ -157,3 +152,60 @@ export class Telemetry {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Language Service client errors
|
||||
* @class LanguageClientErrorHandler
|
||||
*/
|
||||
export class LanguageClientErrorHandler implements ErrorHandler {
|
||||
|
||||
/**
|
||||
* Show an error message prompt with a link to known issues wiki page
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
showOnErrorPrompt(): void {
|
||||
Telemetry.sendTelemetryEvent(Constants.serviceName + 'Crash');
|
||||
vscode.window.showErrorMessage(
|
||||
Constants.serviceCrashMessage,
|
||||
Constants.serviceCrashButton).then(action => {
|
||||
if (action && action === Constants.serviceCrashButton) {
|
||||
opener(Constants.serviceCrashLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client error
|
||||
*
|
||||
* @param {Error} error
|
||||
* @param {Message} message
|
||||
* @param {number} count
|
||||
* @returns {ErrorAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
error(error: Error, message: Message, count: number): ErrorAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return ErrorAction.Shutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client closed
|
||||
*
|
||||
* @returns {CloseAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
closed(): CloseAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return CloseAction.DoNotRestart;
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.initialize();
|
||||
7
extensions/mssql/src/typings/refs.d.ts
vendored
Normal file
7
extensions/mssql/src/typings/refs.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
103
extensions/mssql/src/utils.ts
Normal file
103
extensions/mssql/src/utils.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
|
||||
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
|
||||
// work for now because the extension is running in different process.
|
||||
export function getAppDataPath() {
|
||||
let platform = process.platform;
|
||||
switch (platform) {
|
||||
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
|
||||
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
default: throw new Error('Platform not supported');
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultLogLocation() {
|
||||
return path.join(getAppDataPath(), 'sqlops');
|
||||
}
|
||||
|
||||
export function ensure(target: object, key: string): any {
|
||||
if (target[key] === void 0) {
|
||||
target[key] = {} as any;
|
||||
}
|
||||
return target[key];
|
||||
}
|
||||
|
||||
export interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
export function getPackageInfo(packageJson: any): IPackageInfo {
|
||||
if (packageJson) {
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
aiKey: packageJson.aiKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function generateUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
try {
|
||||
let interfaces = os.networkInterfaces();
|
||||
let mac;
|
||||
for (let key of Object.keys(interfaces)) {
|
||||
let item = interfaces[key][0];
|
||||
if (!item.internal) {
|
||||
mac = item.mac;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mac) {
|
||||
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(generateGuid());
|
||||
}
|
||||
} catch (err) {
|
||||
resolve(generateGuid()); // fallback
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
export function verifyPlatform(): Thenable<boolean> {
|
||||
if (os.platform() === 'darwin' && parseFloat(os.release()) < 16.0) {
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user