mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -05:00
Implement a no sync rule (#7216)
* implement a no sync rule * fix linting disable * fix unused imports * exclude more testing * clean up fs usage * clean up more fs usage * remove duplicate of code * fix compile errors
This commit is contained in:
33
build/lib/tslint/noSyncRule.js
Normal file
33
build/lib/tslint/noSyncRule.js
Normal file
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Lint = require("tslint");
|
||||
const minimatch = require("minimatch");
|
||||
class Rule extends Lint.Rules.AbstractRule {
|
||||
apply(sourceFile) {
|
||||
const args = this.getOptions().ruleArguments[0];
|
||||
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
|
||||
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
exports.Rule = Rule;
|
||||
class NoSyncRuleWalker extends Lint.RuleWalker {
|
||||
constructor(file, opts) {
|
||||
super(file, opts);
|
||||
}
|
||||
visitCallExpression(node) {
|
||||
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
|
||||
this.addFailureAtNode(node, `Do not use Sync operations`);
|
||||
}
|
||||
super.visitCallExpression(node);
|
||||
}
|
||||
}
|
||||
NoSyncRuleWalker.operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
|
||||
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
|
||||
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
|
||||
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];
|
||||
45
build/lib/tslint/noSyncRule.ts
Normal file
45
build/lib/tslint/noSyncRule.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import * as Lint from 'tslint';
|
||||
import * as minimatch from 'minimatch';
|
||||
|
||||
interface NoSyncRuleConfig {
|
||||
exclude: string[];
|
||||
}
|
||||
|
||||
export class Rule extends Lint.Rules.AbstractRule {
|
||||
|
||||
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
|
||||
const args = <NoSyncRuleConfig>this.getOptions().ruleArguments[0];
|
||||
|
||||
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
|
||||
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class NoSyncRuleWalker extends Lint.RuleWalker {
|
||||
|
||||
private static readonly operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
|
||||
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
|
||||
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
|
||||
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];
|
||||
|
||||
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
|
||||
super(file, opts);
|
||||
}
|
||||
|
||||
visitCallExpression(node: ts.CallExpression) {
|
||||
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
|
||||
this.addFailureAtNode(node, `Do not use Sync operations`);
|
||||
}
|
||||
|
||||
super.visitCallExpression(node);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,7 @@ import * as vscode from 'vscode';
|
||||
import { TelemetryReporter, TelemetryViews } from './telemetry';
|
||||
import { doubleEscapeSingleQuotes, backEscapeDoubleQuotes, getTelemetryErrorType } from './utils';
|
||||
import { ChildProcess, exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { readFile } from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -68,7 +67,7 @@ export interface LaunchSsmsDialogParams {
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
// This is for Windows-specific support so do nothing on other platforms
|
||||
if (process.platform === 'win32') {
|
||||
const rawConfig = await promisify(readFile)(path.join(context.extensionPath, 'config.json'));
|
||||
const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
|
||||
const ssmsMinVer = JSON.parse(rawConfig.toString()).version;
|
||||
exePath = path.join(context.extensionPath, 'ssmsmin', 'Windows', ssmsMinVer, 'ssmsmin.exe');
|
||||
registerCommands(context);
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
export class AgentUtils {
|
||||
|
||||
@@ -52,18 +50,11 @@ export class AgentUtils {
|
||||
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return promisify(fs.exists)(path);
|
||||
}
|
||||
|
||||
export function mkdir(path: string): Promise<void> {
|
||||
return promisify(fs.mkdir)(path);
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return promisify(fs.unlink)(path);
|
||||
}
|
||||
|
||||
export function writeFile(path: string, data: string): Promise<void> {
|
||||
return promisify(fs.writeFile)(path, data);
|
||||
export async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { AgentUtils, exists } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
import { NotebookDialogOptions } from '../dialogs/notebookDialog';
|
||||
import { createConnection } from 'net';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const NotebookCompletionActionCondition_Always: string = localize('notebookData.whenJobCompletes', 'When the notebook completes');
|
||||
@@ -179,7 +177,7 @@ export class NotebookData implements IAgentDialogData {
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): { valid: boolean, errorMessages: string[] } {
|
||||
public async validate(): Promise<{ valid: boolean, errorMessages: string[] }> {
|
||||
let validationErrors: string[] = [];
|
||||
if (this.dialogMode !== AgentDialogMode.EDIT) {
|
||||
if (!(this.name && this.name.trim())) {
|
||||
@@ -188,7 +186,7 @@ export class NotebookData implements IAgentDialogData {
|
||||
if (!(this.templatePath && this.name.trim())) {
|
||||
validationErrors.push(TemplatePathEmptyErrorMessage);
|
||||
}
|
||||
if (!fs.existsSync(this.templatePath)) {
|
||||
if (!(await exists(this.templatePath))) {
|
||||
validationErrors.push(InvalidNotebookPathErrorMessage);
|
||||
}
|
||||
if (NotebookData.jobLists) {
|
||||
@@ -201,7 +199,7 @@ export class NotebookData implements IAgentDialogData {
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.templatePath && this.templatePath !== '' && !fs.existsSync(this.templatePath)) {
|
||||
if (this.templatePath && this.templatePath !== '' && !(await exists(this.templatePath))) {
|
||||
validationErrors.push(InvalidNotebookPathErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ export class NotebookDialog extends AgentDialog<NotebookData> {
|
||||
this.generalTab = azdata.window.createTab(GeneralTabText);
|
||||
this.initializeGeneralTab();
|
||||
this.dialog.content = [this.generalTab];
|
||||
this.dialog.registerCloseValidator(() => {
|
||||
this.dialog.registerCloseValidator(async () => {
|
||||
this.updateModel();
|
||||
let validationResult = this.model.validate();
|
||||
let validationResult = await this.model.validate();
|
||||
if (!validationResult.valid) {
|
||||
// TODO: Show Error Messages
|
||||
this.dialog.message = { text: validationResult.errorMessages[0] };
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { AlertDialog } from './dialogs/alertDialog';
|
||||
@@ -17,9 +17,8 @@ import { ProxyDialog } from './dialogs/proxyDialog';
|
||||
import { JobStepDialog } from './dialogs/jobStepDialog';
|
||||
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
||||
import { JobData } from './data/jobData';
|
||||
import { AgentUtils, exists, mkdir, unlink, writeFile } from './agentUtils';
|
||||
import { AgentUtils, exists } from './agentUtils';
|
||||
import { NotebookDialog, NotebookDialogOptions } from './dialogs/notebookDialog';
|
||||
import { promisify } from 'util';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -102,7 +101,7 @@ export class MainController {
|
||||
let templateMap = this.notebookTemplateMap.get(nbEditor.document.uri.toString());
|
||||
let vsEditor = await vscode.workspace.openTextDocument(templateMap.fileUri);
|
||||
let content = vsEditor.getText();
|
||||
promisify(fs.writeFile)(templateMap.tempPath, content);
|
||||
await fs.writeFile(templateMap.tempPath, content);
|
||||
AgentUtils.getAgentService().then(async (agentService) => {
|
||||
let result = await agentService.updateNotebook(templateMap.ownerUri, templateMap.notebookInfo.name, templateMap.notebookInfo, templateMap.tempPath);
|
||||
if (result.success) {
|
||||
@@ -128,14 +127,13 @@ export class MainController {
|
||||
vscode.commands.registerCommand('agent.openNotebookEditorFromJsonString', async (filename: string, jsonNotebook: string, notebookInfo?: azdata.AgentNotebookInfo, ownerUri?: string) => {
|
||||
const tempfilePath = path.join(os.tmpdir(), 'mssql_notebooks', filename + '.ipynb');
|
||||
if (!await exists(path.join(os.tmpdir(), 'mssql_notebooks'))) {
|
||||
await mkdir(path.join(os.tmpdir(), 'mssql_notebooks'));
|
||||
await fs.mkdir(path.join(os.tmpdir(), 'mssql_notebooks'));
|
||||
}
|
||||
let editors = azdata.nb.visibleNotebookEditors;
|
||||
if (await exists(tempfilePath)) {
|
||||
await unlink(tempfilePath);
|
||||
await fs.unlink(tempfilePath);
|
||||
}
|
||||
try {
|
||||
await writeFile(tempfilePath, jsonNotebook);
|
||||
await fs.writeFile(tempfilePath, jsonNotebook);
|
||||
let uri = vscode.Uri.parse(`untitled:${path.basename(tempfilePath)}`);
|
||||
if (notebookInfo) {
|
||||
this.notebookTemplateMap.set(uri.toString(), { notebookInfo: notebookInfo, fileUri: uri, ownerUri: ownerUri, tempPath: tempfilePath });
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as adal from 'adal-node';
|
||||
import * as azdata from 'azdata';
|
||||
import * as crypto from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
export default class TokenCache implements adal.TokenCache {
|
||||
private static CipherAlgorithm = 'aes-256-cbc';
|
||||
@@ -58,23 +58,19 @@ export default class TokenCache implements adal.TokenCache {
|
||||
});
|
||||
}
|
||||
|
||||
public clear(): Thenable<void> {
|
||||
let self = this;
|
||||
public async clear(): Promise<void> {
|
||||
|
||||
// 1) Delete encrypted serialization file
|
||||
// If we got an 'ENOENT' response, the file doesn't exist, which is fine
|
||||
// 3) Delete the encryption key
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.unlink(self._cacheSerializationPath, err => {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => { return self._credentialProvider.deleteCredential(self._credentialServiceKey); })
|
||||
.then(() => { });
|
||||
try {
|
||||
await fs.unlink(this._cacheSerializationPath);
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
await this._credentialProvider.deleteCredential(this._credentialServiceKey);
|
||||
}
|
||||
|
||||
public find(query: any, callback: (error: Error, results: any[]) => void): void {
|
||||
@@ -230,7 +226,7 @@ export default class TokenCache implements adal.TokenCache {
|
||||
});
|
||||
}
|
||||
|
||||
private readCache(): Thenable<adal.TokenResponse[]> {
|
||||
private async readCache(): Promise<adal.TokenResponse[]> {
|
||||
let self = this;
|
||||
|
||||
// NOTE: File system operations are performed synchronously to avoid annoying nested callbacks
|
||||
@@ -239,13 +235,13 @@ export default class TokenCache implements adal.TokenCache {
|
||||
// 3) Decrypt the file contents
|
||||
// 4) Deserialize and return
|
||||
return this.getOrCreateEncryptionParams()
|
||||
.then(encryptionParams => {
|
||||
.then(async encryptionParams => {
|
||||
try {
|
||||
return self.decryptCache('utf8', encryptionParams);
|
||||
} catch (e) {
|
||||
try {
|
||||
// try to parse using 'binary' encoding and rewrite cache as UTF8
|
||||
let response = self.decryptCache('binary', encryptionParams);
|
||||
let response = await self.decryptCache('binary', encryptionParams);
|
||||
self.writeCache(response);
|
||||
return response;
|
||||
} catch (e) {
|
||||
@@ -260,17 +256,17 @@ export default class TokenCache implements adal.TokenCache {
|
||||
});
|
||||
}
|
||||
|
||||
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] {
|
||||
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions);
|
||||
private async decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): Promise<adal.TokenResponse[]> {
|
||||
let cacheCipher = await fs.readFile(this._cacheSerializationPath, TokenCache.FsOptions);
|
||||
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
|
||||
let cacheJson = decipher.update(cacheCipher, 'hex', encoding);
|
||||
let cacheJson = decipher.update(cacheCipher.toString(), 'hex', encoding);
|
||||
cacheJson += decipher.final(encoding);
|
||||
|
||||
// Deserialize the JSON into the array of tokens
|
||||
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
|
||||
for (let objIndex in cacheObj) {
|
||||
for (const obj of cacheObj) {
|
||||
// Rehydrate Date objects since they will always serialize as a string
|
||||
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
|
||||
obj.expiresOn = new Date(<string>obj.expiresOn);
|
||||
}
|
||||
|
||||
return cacheObj;
|
||||
@@ -297,7 +293,7 @@ export default class TokenCache implements adal.TokenCache {
|
||||
// 4) Encrypt the JSON
|
||||
// 3) Write to the file
|
||||
return this.getOrCreateEncryptionParams()
|
||||
.then(encryptionParams => {
|
||||
.then(async encryptionParams => {
|
||||
try {
|
||||
let cacheJson = JSON.stringify(cache);
|
||||
|
||||
@@ -305,7 +301,7 @@ export default class TokenCache implements adal.TokenCache {
|
||||
let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
|
||||
cacheCipher += cipher.final('hex');
|
||||
|
||||
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
|
||||
await fs.writeFile(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as constants from './constants';
|
||||
@@ -54,12 +54,12 @@ function pushDisposable(disposable: vscode.Disposable): void {
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
extensionContext = context;
|
||||
const apiWrapper = new ApiWrapper();
|
||||
let appContext = new AppContext(extensionContext, apiWrapper);
|
||||
|
||||
let storagePath = findOrMakeStoragePath();
|
||||
let storagePath = await findOrMakeStoragePath();
|
||||
if (!storagePath) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -82,12 +82,21 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
};
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the folder for storing the token caches
|
||||
function findOrMakeStoragePath() {
|
||||
async function findOrMakeStoragePath() {
|
||||
let storagePath = path.join(getDefaultLogLocation(), constants.extensionName);
|
||||
try {
|
||||
if (!fs.existsSync(storagePath)) {
|
||||
fs.mkdirSync(storagePath);
|
||||
if (!(await exists(storagePath))) {
|
||||
await fs.mkdir(storagePath);
|
||||
console.log('Initialized Azure account extension storage.');
|
||||
}
|
||||
}
|
||||
@@ -124,4 +133,3 @@ function registerCommands(appContext: AppContext, azureResourceTree: AzureResour
|
||||
|
||||
registerAzureResourceDatabaseCommands(appContext);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||
import { ServerProvider, Events } from 'service-downloader';
|
||||
import { ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
@@ -18,8 +18,7 @@ import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import * as Constants from '../constants';
|
||||
import { TelemetryFeature, FlatFileImportFeature } from './features';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
import { promisify } from 'util';
|
||||
import { readFile } from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
export class ServiceClient {
|
||||
private statusView: vscode.StatusBarItem;
|
||||
@@ -29,7 +28,7 @@ export class ServiceClient {
|
||||
}
|
||||
|
||||
public async startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
|
||||
const rawConfig = await promisify(readFile)(path.join(context.extensionPath, 'config.json'));
|
||||
const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
|
||||
const config = JSON.parse(rawConfig.toString());
|
||||
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
|
||||
@@ -28,6 +28,7 @@ import { registerBooksWidget } from './dashboard/bookWidget';
|
||||
import { createMssqlApi } from './mssqlApiFactory';
|
||||
import { localize } from './localize';
|
||||
import { SqlToolsServer } from './sqlToolsServer';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', 'This sample code loads the file into a data frame and shows the first 10 results.');
|
||||
|
||||
@@ -42,8 +43,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<IExten
|
||||
}
|
||||
|
||||
// ensure our log path exists
|
||||
if (!(await Utils.pfs.exists(context.logPath))) {
|
||||
await Utils.pfs.mkdir(context.logPath);
|
||||
if (!(await Utils.exists(context.logPath))) {
|
||||
await fs.mkdir(context.logPath);
|
||||
}
|
||||
|
||||
let prompter: IPrompter = new CodeAdapter();
|
||||
@@ -200,7 +201,7 @@ async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promi
|
||||
async function handleOpenClusterStatusNotebookTask(profile: azdata.IConnectionProfile, appContext: AppContext): Promise<void> {
|
||||
const notebookRelativePath: string = 'notebooks/tsg/cluster-status.ipynb';
|
||||
const notebookFullPath: string = path.join(appContext.extensionContext.extensionPath, notebookRelativePath);
|
||||
if (!Utils.fileExists(notebookFullPath)) {
|
||||
if (!(await Utils.exists(notebookFullPath))) {
|
||||
vscode.window.showErrorMessage(localize("fileNotFound", "Unable to find the file specified"));
|
||||
} else {
|
||||
const title: string = Utils.findNextUntitledEditorName(notebookFullPath);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as fspath from 'path';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -22,7 +22,7 @@ import { AppContext } from '../appContext';
|
||||
import { TreeNode } from './treeNodes';
|
||||
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider';
|
||||
|
||||
function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): vscode.Uri {
|
||||
async function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): Promise<vscode.Uri> {
|
||||
let root = utils.getUserHome();
|
||||
let workspaceFolders = apiWrapper.workspaceFolders;
|
||||
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||
@@ -33,7 +33,7 @@ function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: bo
|
||||
let fileNum = 1;
|
||||
let fileNameWithoutExtension = fspath.parse(fileName).name;
|
||||
let fileExtension = fspath.parse(fileName).ext;
|
||||
while (fs.existsSync(fspath.join(root, fileName))) {
|
||||
while (await utils.exists(fspath.join(root, fileName))) {
|
||||
fileName = `${fileNameWithoutExtension}-${fileNum}${fileExtension}`;
|
||||
fileNum++;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export class UploadFilesCommand extends ProgressCommand {
|
||||
};
|
||||
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
|
||||
if (fileUris) {
|
||||
let files: IFile[] = fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles());
|
||||
let files: IFile[] = await Promise.all(fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles()));
|
||||
await this.executeWithProgress(
|
||||
(cancelToken: vscode.CancellationTokenSource) => this.writeFiles(files, folderNode, cancelToken),
|
||||
localize('uploading', 'Uploading files to HDFS'), true,
|
||||
@@ -99,9 +99,9 @@ export class UploadFilesCommand extends ProgressCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private mapPathsToFiles(): (value: string, index: number, array: string[]) => File {
|
||||
return (path: string) => {
|
||||
let isDir = fs.lstatSync(path).isDirectory();
|
||||
private mapPathsToFiles(): (value: string, index: number, array: string[]) => Promise<File> {
|
||||
return async (path: string) => {
|
||||
let isDir = (await fs.lstat(path)).isDirectory();
|
||||
return new File(path, isDir);
|
||||
};
|
||||
}
|
||||
@@ -115,9 +115,9 @@ export class UploadFilesCommand extends ProgressCommand {
|
||||
if (file.isDirectory) {
|
||||
let dirName = fspath.basename(file.path);
|
||||
let subFolder = await folderNode.mkdir(dirName);
|
||||
let children: IFile[] = fs.readdirSync(file.path)
|
||||
let children: IFile[] = await Promise.all((await fs.readdir(file.path))
|
||||
.map(childFileName => joinHdfsPath(file.path, childFileName))
|
||||
.map(this.mapPathsToFiles());
|
||||
.map(this.mapPathsToFiles()));
|
||||
this.writeFiles(children, subFolder, cancelToken);
|
||||
} else {
|
||||
await folderNode.writeFile(file);
|
||||
@@ -258,7 +258,7 @@ export class SaveFileCommand extends ProgressCommand {
|
||||
try {
|
||||
let fileNode = await getNode<FileNode>(context, this.appContext);
|
||||
if (fileNode) {
|
||||
let defaultUri = getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath));
|
||||
let defaultUri = await getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath));
|
||||
let fileUri: vscode.Uri = await this.apiWrapper.showSaveDialog({
|
||||
defaultUri: defaultUri
|
||||
});
|
||||
@@ -330,7 +330,7 @@ export class PreviewFileCommand extends ProgressCommand {
|
||||
private async showNotebookDocument(fileName: string, connectionProfile?: azdata.IConnectionProfile,
|
||||
initialContent?: string
|
||||
): Promise<azdata.nb.NotebookEditor> {
|
||||
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true)
|
||||
let docUri: vscode.Uri = (await getSaveableUri(this.apiWrapper, fileName, true))
|
||||
.with({ scheme: constants.UNTITLED_SCHEMA });
|
||||
return await azdata.nb.showNotebookDocument(docUri, {
|
||||
connectionProfile: connectionProfile,
|
||||
@@ -340,7 +340,7 @@ export class PreviewFileCommand extends ProgressCommand {
|
||||
}
|
||||
|
||||
private async openTextDocument(fileName: string): Promise<vscode.TextDocument> {
|
||||
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true);
|
||||
let docUri: vscode.Uri = await getSaveableUri(this.apiWrapper, fileName, true);
|
||||
if (docUri) {
|
||||
docUri = docUri.with({ scheme: constants.UNTITLED_SCHEMA });
|
||||
return await this.apiWrapper.openTextDocument(docUri);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as fspath from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
import * as utils from '../../../utils';
|
||||
import * as LocalizedConstants from '../../../localizedConstants';
|
||||
@@ -223,7 +222,7 @@ export class SparkConfigurationTab {
|
||||
|
||||
// 1. For local file Source check whether they existed.
|
||||
if (this._dataModel.isMainSourceFromLocal) {
|
||||
if (!fs.existsSync(this._dataModel.localFileSourcePath)) {
|
||||
if (!(await utils.exists(this._dataModel.localFileSourcePath))) {
|
||||
this._dataModel.showDialogError(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(this._dataModel.localFileSourcePath));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as fs from 'fs';
|
||||
import * as fspath from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
@@ -143,7 +142,7 @@ export class SparkJobSubmissionModel {
|
||||
return Promise.reject(localize('sparkJobSubmission_localFileOrFolderNotSpecified.', 'Property localFilePath or hdfsFolderPath is not specified. '));
|
||||
}
|
||||
|
||||
if (!fs.existsSync(localFilePath)) {
|
||||
if (!(await utils.exists(localFilePath))) {
|
||||
return Promise.reject(LocalizedConstants.sparkJobSubmissionLocalFileNotExisted(localFilePath));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,12 +45,12 @@ export function getTemplatePath(extensionPath: string, templateName: string): st
|
||||
}
|
||||
export function shellWhichResolving(cmd: string): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
which(cmd, (err, foundPath) => {
|
||||
which(cmd, async (err, foundPath) => {
|
||||
if (err) {
|
||||
resolve(undefined);
|
||||
} else {
|
||||
// NOTE: Using realpath b/c some system installs are symlinked from */bin
|
||||
resolve(fs.realpathSync(foundPath));
|
||||
resolve(await fs.promises.realpath(foundPath));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,8 +20,7 @@ import { AppContext } from './appContext';
|
||||
import { DacFxService } from './dacfx/dacFxService';
|
||||
import { CmsService } from './cms/cmsService';
|
||||
import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts';
|
||||
import { promisify } from 'util';
|
||||
import { readFile } from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||
const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
@@ -70,7 +69,7 @@ export class SqlToolsServer {
|
||||
}
|
||||
|
||||
private async download(context: AppContext): Promise<string> {
|
||||
const rawConfig = await promisify(readFile)(path.join(context.extensionContext.extensionPath, 'config.json'));
|
||||
const rawConfig = await fs.readFile(path.join(context.extensionContext.extensionPath, 'config.json'));
|
||||
this.config = JSON.parse(rawConfig.toString());
|
||||
this.config.installDirectory = path.join(__dirname, this.config.installDirectory);
|
||||
this.config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
|
||||
@@ -10,8 +10,7 @@ import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
import * as findRemoveSync from 'find-remove';
|
||||
import * as constants from './constants';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { promises as fs } from 'fs';
|
||||
|
||||
const configTracingLevel = 'tracingLevel';
|
||||
const configLogRetentionMinutes = 'logRetentionMinutes';
|
||||
@@ -31,17 +30,6 @@ export function getAppDataPath() {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace pfs {
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return promisify(fs.exists)(path);
|
||||
}
|
||||
|
||||
export function mkdir(path: string, mode?: number): Promise<void> {
|
||||
return promisify(fs.mkdir)(path, mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file name that is not already used in the target directory
|
||||
* @param filePath source notebook file name
|
||||
@@ -61,14 +49,6 @@ export function findNextUntitledEditorName(filePath: string): string {
|
||||
return title;
|
||||
}
|
||||
|
||||
export function fileExists(file: string): boolean {
|
||||
return fs.existsSync(file);
|
||||
}
|
||||
|
||||
export function copyFile(source: string, target: string): void {
|
||||
fs.copyFileSync(source, target);
|
||||
}
|
||||
|
||||
export function removeOldLogFiles(logPath: string, prefix: string): JSON {
|
||||
return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
|
||||
}
|
||||
@@ -303,3 +283,12 @@ export function logDebug(msg: any): void {
|
||||
console.log(outputMsg);
|
||||
}
|
||||
}
|
||||
|
||||
export async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ export class BookTreeItem extends vscode.TreeItem {
|
||||
if (this.book.tableOfContents.sections[i].url) {
|
||||
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
|
||||
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
|
||||
// tslint:disable-next-line:no-sync
|
||||
if (fs.existsSync(pathToNotebook)) {
|
||||
this._previousUri = pathToNotebook;
|
||||
return;
|
||||
@@ -93,6 +94,7 @@ export class BookTreeItem extends vscode.TreeItem {
|
||||
if (this.book.tableOfContents.sections[i].url) {
|
||||
// TODO: Currently only navigating to notebooks. Need to add logic for markdown.
|
||||
let pathToNotebook = path.join(this.book.root, 'content', this.book.tableOfContents.sections[i].url.concat('.ipynb'));
|
||||
// tslint:disable-next-line:no-sync
|
||||
if (fs.existsSync(pathToNotebook)) {
|
||||
this._nextUri = pathToNotebook;
|
||||
return;
|
||||
|
||||
@@ -6,18 +6,16 @@
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as glob from 'fast-glob';
|
||||
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
|
||||
import { maxBookSearchDepth, notebookConfigKey } from '../common/constants';
|
||||
import { isEditorTitleFree } from '../common/utils';
|
||||
import { isEditorTitleFree, exists } from '../common/utils';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { promisify } from 'util';
|
||||
import { IJupyterBookToc, IJupyterBookSection } from '../contracts/content';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const existsAsync = promisify(fs.exists);
|
||||
|
||||
export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem>, azdata.nb.NavigationProvider {
|
||||
readonly providerId: string = 'BookNavigator';
|
||||
@@ -84,7 +82,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
const bookViewer = vscode.window.createTreeView('bookTreeView', { showCollapseAll: true, treeDataProvider: this });
|
||||
await vscode.commands.executeCommand('workbench.books.action.focusBooksExplorer');
|
||||
this._openAsUntitled = openAsUntitled;
|
||||
let books = this.getBooks();
|
||||
let books = await this.getBooks();
|
||||
if (books && books.length > 0) {
|
||||
const rootTreeItem = books[0];
|
||||
const sectionToOpen = rootTreeItem.findChildSection(urlToOpen);
|
||||
@@ -92,8 +90,8 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
const urlPath = sectionToOpen ? sectionToOpen.url : rootTreeItem.tableOfContents.sections[0].url;
|
||||
const sectionToOpenMarkdown: string = path.join(bookPath, 'content', urlPath.concat('.md'));
|
||||
const sectionToOpenNotebook: string = path.join(bookPath, 'content', urlPath.concat('.ipynb'));
|
||||
const markdownExists = await existsAsync(sectionToOpenMarkdown);
|
||||
const notebookExists = await existsAsync(sectionToOpenNotebook);
|
||||
const markdownExists = await exists(sectionToOpenMarkdown);
|
||||
const notebookExists = await exists(sectionToOpenNotebook);
|
||||
if (markdownExists) {
|
||||
vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(sectionToOpenMarkdown));
|
||||
}
|
||||
@@ -208,13 +206,13 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
return array.reduce((acc, val) => Array.isArray(val.sections) ? acc.concat(val).concat(this.parseJupyterSection(val.sections)) : acc.concat(val), []);
|
||||
}
|
||||
|
||||
public getBooks(): BookTreeItem[] {
|
||||
public async getBooks(): Promise<BookTreeItem[]> {
|
||||
let books: BookTreeItem[] = [];
|
||||
for (let i in this._tableOfContentPaths) {
|
||||
let root = path.dirname(path.dirname(this._tableOfContentPaths[i]));
|
||||
for (const contentPath of this._tableOfContentPaths) {
|
||||
let root = path.dirname(path.dirname(contentPath));
|
||||
try {
|
||||
const config = yaml.safeLoad(fs.readFileSync(path.join(root, '_config.yml'), 'utf-8'));
|
||||
const tableOfContents = yaml.safeLoad(fs.readFileSync(this._tableOfContentPaths[i], 'utf-8'));
|
||||
const config = yaml.safeLoad((await fs.readFile(path.join(root, '_config.yml'), 'utf-8')).toString());
|
||||
const tableOfContents = yaml.safeLoad((await fs.readFile(contentPath, 'utf-8')).toString());
|
||||
try {
|
||||
let book = new BookTreeItem({
|
||||
title: config.title,
|
||||
@@ -242,7 +240,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
return books;
|
||||
}
|
||||
|
||||
public getSections(tableOfContents: IJupyterBookToc, sections: IJupyterBookSection[], root: string): BookTreeItem[] {
|
||||
public async getSections(tableOfContents: IJupyterBookToc, sections: IJupyterBookSection[], root: string): Promise<BookTreeItem[]> {
|
||||
let notebooks: BookTreeItem[] = [];
|
||||
for (let i = 0; i < sections.length; i++) {
|
||||
if (sections[i].url) {
|
||||
@@ -267,7 +265,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
let pathToMarkdown = path.join(root, 'content', sections[i].url.concat('.md'));
|
||||
// Note: Currently, if there is an ipynb and a md file with the same name, Jupyter Books only shows the notebook.
|
||||
// Following Jupyter Books behavior for now
|
||||
if (fs.existsSync(pathToNotebook)) {
|
||||
if (await exists(pathToNotebook)) {
|
||||
let notebook = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
root: root,
|
||||
@@ -286,7 +284,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
if (this._openAsUntitled) {
|
||||
this._allNotebooks.set(path.basename(pathToNotebook), notebook);
|
||||
}
|
||||
} else if (fs.existsSync(pathToMarkdown)) {
|
||||
} else if (await exists(pathToMarkdown)) {
|
||||
let markdown = new BookTreeItem({
|
||||
title: sections[i].title,
|
||||
root: root,
|
||||
|
||||
@@ -188,3 +188,12 @@ export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
|
||||
port: undefined
|
||||
};
|
||||
}
|
||||
|
||||
export async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as fs from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as utils from '../common/utils';
|
||||
|
||||
import { JupyterServerInstallation } from '../jupyter/jupyterServerInstallation';
|
||||
@@ -220,7 +220,7 @@ export class ConfigurePythonDialog {
|
||||
|
||||
if (useExistingPython) {
|
||||
let exePath = JupyterServerInstallation.getPythonExePath(pythonLocation, true);
|
||||
let pythonExists = fs.existsSync(exePath);
|
||||
let pythonExists = await utils.exists(exePath);
|
||||
if (!pythonExists) {
|
||||
this.showErrorMessage(this.PythonNotFoundMsg);
|
||||
return false;
|
||||
@@ -243,27 +243,23 @@ export class ConfigurePythonDialog {
|
||||
return true;
|
||||
}
|
||||
|
||||
private isFileValid(pythonLocation: string): Promise<boolean> {
|
||||
private async isFileValid(pythonLocation: string): Promise<boolean> {
|
||||
let self = this;
|
||||
return new Promise<boolean>(function (resolve) {
|
||||
fs.stat(pythonLocation, function (err, stats) {
|
||||
if (err) {
|
||||
// Ignore error if folder doesn't exist, since it will be
|
||||
// created during installation
|
||||
if (err.code !== 'ENOENT') {
|
||||
self.showErrorMessage(err.message);
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (stats.isFile()) {
|
||||
self.showErrorMessage(self.InvalidLocationMsg);
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
try {
|
||||
const stats = await fs.stat(pythonLocation);
|
||||
if (stats.isFile()) {
|
||||
self.showErrorMessage(self.InvalidLocationMsg);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
// Ignore error if folder doesn't exist, since it will be
|
||||
// created during installation
|
||||
if (err.code !== 'ENOENT') {
|
||||
self.showErrorMessage(err.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async handleBrowse(): Promise<void> {
|
||||
@@ -304,4 +300,4 @@ export class ConfigurePythonDialog {
|
||||
level: azdata.window.MessageLevel.Error
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as glob from 'glob';
|
||||
|
||||
import * as utils from '../common/utils';
|
||||
@@ -92,7 +91,7 @@ export class PythonPathLookup {
|
||||
const cmd = `"${options.command}" ${args.join(' ')}`;
|
||||
let output = await utils.executeBufferedCommand(cmd, {});
|
||||
let value = output ? output.trim() : '';
|
||||
if (value.length > 0 && fs.existsSync(value)) {
|
||||
if (value.length > 0 && await utils.exists(value)) {
|
||||
return value;
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -163,4 +162,4 @@ export class PythonPathLookup {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class JupyterServerInstallation {
|
||||
}
|
||||
|
||||
private async installDependencies(backgroundOperation: azdata.BackgroundOperation): Promise<void> {
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) {
|
||||
if (!(await utils.exists(this._pythonExecutable)) || this._forceInstall || this._usingExistingPython) {
|
||||
window.showInformationMessage(msgInstallPkgStart);
|
||||
|
||||
this.outputChannel.show(true);
|
||||
@@ -180,11 +180,11 @@ export class JupyterServerInstallation {
|
||||
});
|
||||
|
||||
downloadRequest.pipe(fs.createWriteStream(pythonPackagePathLocal))
|
||||
.on('close', () => {
|
||||
.on('close', async () => {
|
||||
//unpack python zip/tar file
|
||||
this.outputChannel.appendLine(msgPythonUnpackPending);
|
||||
let pythonSourcePath = path.join(installPath, constants.pythonBundleVersion);
|
||||
if (!this._usingExistingPython && fs.existsSync(pythonSourcePath)) {
|
||||
if (!this._usingExistingPython && await utils.exists(pythonSourcePath)) {
|
||||
try {
|
||||
fs.removeSync(pythonSourcePath);
|
||||
} catch (err) {
|
||||
@@ -256,7 +256,7 @@ export class JupyterServerInstallation {
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(this._pythonExecutable)) {
|
||||
if (await utils.exists(this._pythonExecutable)) {
|
||||
let pythonUserDir = await this.getPythonUserDir(this._pythonExecutable);
|
||||
if (pythonUserDir) {
|
||||
this.pythonEnvVarPath = pythonUserDir + delimiter + this.pythonEnvVarPath;
|
||||
@@ -330,7 +330,7 @@ export class JupyterServerInstallation {
|
||||
await this.configurePackagePaths();
|
||||
};
|
||||
let installReady = new Deferred<void>();
|
||||
if (!fs.existsSync(this._pythonExecutable) || this._forceInstall || this._usingExistingPython) {
|
||||
if (!(await utils.exists(this._pythonExecutable)) || this._forceInstall || this._usingExistingPython) {
|
||||
this.apiWrapper.startBackgroundOperation({
|
||||
displayName: msgTaskName,
|
||||
description: msgTaskName,
|
||||
@@ -522,6 +522,7 @@ export class JupyterServerInstallation {
|
||||
}
|
||||
|
||||
let condaExePath = this.getCondaExePath();
|
||||
// tslint:disable-next-line:no-sync
|
||||
return fs.existsSync(condaExePath);
|
||||
}
|
||||
|
||||
@@ -538,6 +539,7 @@ export class JupyterServerInstallation {
|
||||
|
||||
let useExistingInstall = JupyterServerInstallation.getExistingPythonSetting(apiWrapper);
|
||||
let pythonExe = JupyterServerInstallation.getPythonExePath(pathSetting, useExistingInstall);
|
||||
// tslint:disable-next-line:no-sync
|
||||
return fs.existsSync(pythonExe);
|
||||
}
|
||||
|
||||
@@ -568,6 +570,7 @@ export class JupyterServerInstallation {
|
||||
let notebookConfig = apiWrapper.getConfiguration(constants.notebookConfigKey);
|
||||
if (notebookConfig) {
|
||||
let configPythonPath = notebookConfig[constants.pythonPathConfigKey];
|
||||
// tslint:disable-next-line:no-sync
|
||||
if (configPythonPath && fs.existsSync(configPythonPath)) {
|
||||
path = configPythonPath;
|
||||
}
|
||||
|
||||
@@ -62,8 +62,13 @@ export class ServerInstanceUtils {
|
||||
public copy(src: string, dest: string): Promise<void> {
|
||||
return fs.copy(src, dest);
|
||||
}
|
||||
public existsSync(dirPath: string): boolean {
|
||||
return fs.existsSync(dirPath);
|
||||
public async exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public generateUuid(): string {
|
||||
return UUID.generateUuid();
|
||||
@@ -204,7 +209,7 @@ export class PerNotebookServerInstance implements IServerInstance {
|
||||
private async copyKernelsToSystemJupyterDirs(): Promise<void> {
|
||||
let kernelsExtensionSource = path.join(this.options.install.extensionPath, 'kernels');
|
||||
this._systemJupyterDir = path.join(this.getSystemJupyterHomeDir(), 'kernels');
|
||||
if (!this.utils.existsSync(this._systemJupyterDir)) {
|
||||
if (!(await this.utils.exists(this._systemJupyterDir))) {
|
||||
await this.utils.mkDir(this._systemJupyterDir, this.options.install.outputChannel);
|
||||
}
|
||||
await this.utils.copy(kernelsExtensionSource, this._systemJupyterDir);
|
||||
|
||||
@@ -283,9 +283,9 @@ describe.skip('BookTreeViewProviderTests', function() {
|
||||
await Promise.race([tocRead, errorCase.then(() => { throw new Error('Table of Contents were not ready in time'); })]);
|
||||
});
|
||||
|
||||
it('should show error if notebook or markdown file is missing', function(): void {
|
||||
it('should show error if notebook or markdown file is missing', async function(): Promise<void> {
|
||||
let books = bookTreeViewProvider.getBooks();
|
||||
let children = bookTreeViewProvider.getSections({ sections: [] }, books[0].sections, rootFolderPath);
|
||||
let children = await bookTreeViewProvider.getSections({ sections: [] }, (await books)[0].sections, rootFolderPath);
|
||||
should(bookTreeViewProvider.errorMessage).equal('Missing file : Notebook1');
|
||||
// Rest of book should be detected correctly even with a missing file
|
||||
equalBookItems(children[0], expectedNotebook2);
|
||||
|
||||
@@ -57,7 +57,7 @@ describe('Jupyter server instance', function (): void {
|
||||
// Given a server instance
|
||||
mockUtils.setup(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||
mockUtils.setup(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => Promise.resolve());
|
||||
mockUtils.setup(u => u.existsSync(TypeMoq.It.isAnyString())).returns(() => false);
|
||||
mockUtils.setup(u => u.exists(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(false));
|
||||
|
||||
// When I run configure
|
||||
await serverInstance.configure();
|
||||
@@ -65,7 +65,7 @@ describe('Jupyter server instance', function (): void {
|
||||
// Then I expect a folder to have been created with config and data subdirs
|
||||
mockUtils.verify(u => u.mkDir(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(5));
|
||||
mockUtils.verify(u => u.copy(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3));
|
||||
mockUtils.verify(u => u.existsSync(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1));
|
||||
mockUtils.verify(u => u.exists(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(1));
|
||||
});
|
||||
|
||||
it('Should have URI info after start', async function (): Promise<void> {
|
||||
|
||||
@@ -26,10 +26,12 @@ export class PlatformService implements IPlatformService {
|
||||
}
|
||||
|
||||
copyFile(source: string, target: string): void {
|
||||
// tslint:disable-next-line:no-sync
|
||||
fs.copyFileSync(source, target);
|
||||
}
|
||||
|
||||
fileExists(file: string): boolean {
|
||||
// tslint:disable-next-line:no-sync
|
||||
return fs.existsSync(file);
|
||||
}
|
||||
|
||||
@@ -44,4 +46,4 @@ export class PlatformService implements IPlatformService {
|
||||
isNotebookNameUsed(title: string): boolean {
|
||||
return (azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import { createWriteStream, promises as fs } from 'fs';
|
||||
import * as https from 'https';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
@@ -226,7 +226,7 @@ export class ResourceTypeService implements IResourceTypeService {
|
||||
private download(url: string): Promise<string> {
|
||||
const self = this;
|
||||
const promise = new Promise<string>((resolve, reject) => {
|
||||
https.get(url, function (response) {
|
||||
https.get(url, async function (response) {
|
||||
console.log('Download installer from: ' + url);
|
||||
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||
// Redirect and download from new location
|
||||
@@ -247,19 +247,19 @@ export class ResourceTypeService implements IResourceTypeService {
|
||||
let fileName = originalFileName;
|
||||
const downloadFolder = os.homedir();
|
||||
let cnt = 1;
|
||||
while (fs.existsSync(path.join(downloadFolder, fileName + extension))) {
|
||||
while (await exists(path.join(downloadFolder, fileName + extension))) {
|
||||
fileName = `${originalFileName}-${cnt}`;
|
||||
cnt++;
|
||||
}
|
||||
fileName = path.join(downloadFolder, fileName + extension);
|
||||
const file = fs.createWriteStream(fileName);
|
||||
const file = createWriteStream(fileName);
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
resolve(fileName);
|
||||
});
|
||||
file.on('error', (err) => {
|
||||
fs.unlink(fileName, () => { });
|
||||
file.on('error', async (err) => {
|
||||
await fs.unlink(fileName);
|
||||
reject(err.message);
|
||||
});
|
||||
});
|
||||
@@ -268,3 +268,12 @@ export class ResourceTypeService implements IResourceTypeService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.14.8",
|
||||
"@types/node": "^10.14.8",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"typemoq": "^2.1.0",
|
||||
|
||||
@@ -9,8 +9,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as os from 'os';
|
||||
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { existsSync } from 'fs';
|
||||
import { promises as fs } from 'fs';
|
||||
import { Telemetry } from '../telemetry';
|
||||
import { getEndpointName } from '../utils';
|
||||
import * as mssql from '../../../mssql';
|
||||
@@ -35,6 +34,15 @@ const YesButtonText: string = localize('schemaCompareDialog.Yes', 'Yes');
|
||||
const NoButtonText: string = localize('schemaCompareDialog.No', 'No');
|
||||
const titleFontSize: number = 13;
|
||||
|
||||
async function exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class SchemaCompareDialog {
|
||||
public dialog: azdata.window.Dialog;
|
||||
public dialogName: string;
|
||||
@@ -194,8 +202,8 @@ export class SchemaCompareDialog {
|
||||
ariaLabel: localize('schemaCompareDialog.sourceTextBox', "Source file")
|
||||
}).component();
|
||||
|
||||
this.sourceTextBox.onTextChanged((e) => {
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.sourceTextBox.onTextChanged(async (e) => {
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
this.targetTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
@@ -204,8 +212,8 @@ export class SchemaCompareDialog {
|
||||
ariaLabel: localize('schemaCompareDialog.targetTextBox', "Target file")
|
||||
}).component();
|
||||
|
||||
this.targetTextBox.onTextChanged(() => {
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.targetTextBox.onTextChanged(async () => {
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
this.sourceServerComponent = await this.createSourceServerDropdown(view);
|
||||
@@ -307,7 +315,7 @@ export class SchemaCompareDialog {
|
||||
currentButton.onDidClick(async (click) => {
|
||||
// file browser should open where the current dacpac is or the appropriate default folder
|
||||
let rootPath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].name : os.homedir();
|
||||
let defaultUri = endpoint && endpoint.packageFilePath && existsSync(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
|
||||
let defaultUri = endpoint && endpoint.packageFilePath && await exists(endpoint.packageFilePath) ? endpoint.packageFilePath : rootPath;
|
||||
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
@@ -351,17 +359,17 @@ export class SchemaCompareDialog {
|
||||
}).component();
|
||||
|
||||
// show dacpac file browser
|
||||
dacpacRadioButton.onDidClick(() => {
|
||||
dacpacRadioButton.onDidClick(async () => {
|
||||
this.sourceIsDacpac = true;
|
||||
this.formBuilder.removeFormItem(this.sourceNoActiveConnectionsText);
|
||||
this.formBuilder.removeFormItem(this.sourceServerComponent);
|
||||
this.formBuilder.removeFormItem(this.sourceDatabaseComponent);
|
||||
this.formBuilder.insertFormItem(this.sourceDacpacComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
// show server and db dropdowns or 'No active connections' text
|
||||
databaseRadioButton.onDidClick(() => {
|
||||
databaseRadioButton.onDidClick(async () => {
|
||||
this.sourceIsDacpac = false;
|
||||
if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) {
|
||||
this.formBuilder.insertFormItem(this.sourceServerComponent, 2, { horizontal: true, titleFontSize: titleFontSize });
|
||||
@@ -370,7 +378,7 @@ export class SchemaCompareDialog {
|
||||
this.formBuilder.insertFormItem(this.sourceNoActiveConnectionsText, 2, { horizontal: true, titleFontSize: titleFontSize });
|
||||
}
|
||||
this.formBuilder.removeFormItem(this.sourceDacpacComponent);
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
// if source is currently a db, show it in the server and db dropdowns
|
||||
@@ -408,17 +416,17 @@ export class SchemaCompareDialog {
|
||||
}).component();
|
||||
|
||||
// show dacpac file browser
|
||||
dacpacRadioButton.onDidClick(() => {
|
||||
dacpacRadioButton.onDidClick(async () => {
|
||||
this.targetIsDacpac = true;
|
||||
this.formBuilder.removeFormItem(this.targetNoActiveConnectionsText);
|
||||
this.formBuilder.removeFormItem(this.targetServerComponent);
|
||||
this.formBuilder.removeFormItem(this.targetDatabaseComponent);
|
||||
this.formBuilder.addFormItem(this.targetDacpacComponent, { horizontal: true, titleFontSize: titleFontSize });
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
// show server and db dropdowns or 'No active connections' text
|
||||
databaseRadioButton.onDidClick(() => {
|
||||
databaseRadioButton.onDidClick(async () => {
|
||||
this.targetIsDacpac = false;
|
||||
this.formBuilder.removeFormItem(this.targetDacpacComponent);
|
||||
if ((this.targetServerDropdown.value as ConnectionDropdownValue)) {
|
||||
@@ -427,7 +435,7 @@ export class SchemaCompareDialog {
|
||||
} else {
|
||||
this.formBuilder.addFormItem(this.targetNoActiveConnectionsText, { horizontal: true, titleFontSize: titleFontSize });
|
||||
}
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
// if target is currently a db, show it in the server and db dropdowns
|
||||
@@ -450,18 +458,18 @@ export class SchemaCompareDialog {
|
||||
};
|
||||
}
|
||||
|
||||
private shouldEnableOkayButton(): boolean {
|
||||
private async shouldEnableOkayButton(): Promise<boolean> {
|
||||
|
||||
let sourcefilled = (this.sourceIsDacpac && this.existsDacpac(this.sourceTextBox.value))
|
||||
let sourcefilled = (this.sourceIsDacpac && await this.existsDacpac(this.sourceTextBox.value))
|
||||
|| (!this.sourceIsDacpac && !isNullOrUndefined(this.sourceDatabaseDropdown.value) && this.sourceDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.sourceDbEditable)) !== -1);
|
||||
let targetfilled = (this.targetIsDacpac && this.existsDacpac(this.targetTextBox.value))
|
||||
let targetfilled = (this.targetIsDacpac && await this.existsDacpac(this.targetTextBox.value))
|
||||
|| (!this.targetIsDacpac && !isNullOrUndefined(this.targetDatabaseDropdown.value) && this.targetDatabaseDropdown.values.findIndex(x => this.matchesValue(x, this.targetDbEditable)) !== -1);
|
||||
|
||||
return sourcefilled && targetfilled;
|
||||
}
|
||||
|
||||
private existsDacpac(filename: string): boolean {
|
||||
return !isNullOrUndefined(filename) && existsSync(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac'));
|
||||
private async existsDacpac(filename: string): Promise<boolean> {
|
||||
return !isNullOrUndefined(filename) && await exists(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac'));
|
||||
}
|
||||
|
||||
protected async createSourceServerDropdown(view: azdata.ModelView): Promise<azdata.FormComponent> {
|
||||
@@ -596,9 +604,9 @@ export class SchemaCompareDialog {
|
||||
ariaLabel: localize('schemaCompareDialog.sourceDatabaseDropdown', "Source Database")
|
||||
}
|
||||
).component();
|
||||
this.sourceDatabaseDropdown.onValueChanged((value) => {
|
||||
this.sourceDatabaseDropdown.onValueChanged(async (value) => {
|
||||
this.sourceDbEditable = value;
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -615,9 +623,9 @@ export class SchemaCompareDialog {
|
||||
ariaLabel: localize('schemaCompareDialog.targetDatabaseDropdown', "Target Database")
|
||||
}
|
||||
).component();
|
||||
this.targetDatabaseDropdown.onValueChanged((value) => {
|
||||
this.targetDatabaseDropdown.onValueChanged(async (value) => {
|
||||
this.targetDbEditable = value;
|
||||
this.dialog.okButton.enabled = this.shouldEnableOkayButton();
|
||||
this.dialog.okButton.enabled = await this.shouldEnableOkayButton();
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -684,3 +692,7 @@ export class SchemaCompareDialog {
|
||||
interface ConnectionDropdownValue extends azdata.CategoryValue {
|
||||
connection: azdata.connection.ConnectionProfile;
|
||||
}
|
||||
|
||||
function isNullOrUndefined(val: any): boolean {
|
||||
return val === null || val === undefined;
|
||||
}
|
||||
|
||||
15
tslint.json
15
tslint.json
@@ -229,6 +229,21 @@
|
||||
"translation-remind": true,
|
||||
"no-standalone-editor": true,
|
||||
"no-nls-in-standalone-editor": true,
|
||||
"no-sync": [
|
||||
true,
|
||||
{
|
||||
"exclude": [
|
||||
"**/vs/**", // assume they are doing the right thing
|
||||
"**/extensions/git/**", // assume they are doing the right thing,
|
||||
"**/extensions/extension-editing/**", // assume they are doing the right thing,
|
||||
"**/json-language-features/**", // assume they are doing the right thing,
|
||||
"**/vscode-test-resolver/**", // testing doesn't matter
|
||||
"**/integration-tests/**", // testing doesn't matter
|
||||
"**/*.test.*", // testing doesn't matter
|
||||
"**/test/**" // testing doesn't matter
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-useless-strict": true
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
|
||||
Reference in New Issue
Block a user