diff --git a/src/extension.ts b/src/extension.ts index 6f5a5c0..d0d3ad8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,6 +14,7 @@ import { } from './websharkView'; import { TreeViewNode, TreeViewProvider } from './treeViewProvider'; import { filterPcap, extractDlt, removeTecmp } from './filterPcap'; +import { fileExistsOrPick } from './utils'; const extensionId = 'mbehr1.vsc-webshark'; let reporter: TelemetryReporter; @@ -80,63 +81,51 @@ export function activate(context: vscode.ExtensionContext) { // register our command to open pcap files in webshark view: context.subscriptions.push( vscode.commands.registerCommand('webshark.openFile', async () => { - let _sharkdPath = vscode.workspace.getConfiguration().get('vsc-webshark.sharkdFullPath'); - // check if _sharkdPath exists - if (!fileExists(_sharkdPath)) { - vscode.window - .showErrorMessage( - `sharkdFullPath setting not pointing to a file. Please check setting. Currently used: '${_sharkdPath}'`, - { modal: true }, - 'open settings' - ) - .then((value) => { - switch (value) { - case 'open settings': - vscode.commands.executeCommand('workbench.action.openSettings', 'vsc-webshark.sharkdFullPath'); - break; - } - }); - } else { - return vscode.window - .showOpenDialog({ - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - filters: { - 'pcap files': ['pcap', 'cap', 'pcapng'].flatMap((ext) => [ext, ext + '.gz', ext + '.zst', ext + '.lz4']), - }, - openLabel: 'Select pcap file to open...', - }) - .then(async (uris: vscode.Uri[] | undefined) => { - if (uris) { - uris.forEach((uri) => { - console.log(`webshark.openFile got URI=${uri.toString()}`); - const sharkd = new SharkdProcess(_sharkdPath); - sharkd.ready().then((ready) => { - if (ready) { - context.subscriptions.push( - new WebsharkView(undefined, context, treeDataProvider, _onDidChangeSelectedTime, uri, sharkd, activeViews, (r) => { - const idx = activeViews.indexOf(r); - console.log(` openFile dispose called( r idx = ${idx}) activeViews=${activeViews.length}`); - if (idx >= 0) { - activeViews.splice(idx, 1); - } - }) - ); - if (reporter) { - reporter.sendTelemetryEvent('open file', undefined, { err: 0 }); - } - } else { - vscode.window.showErrorMessage(`sharkd connection not ready! Please check setting. Currently used: '${_sharkdPath}'`); - if (reporter) { - reporter.sendTelemetryEvent('open file', undefined, { err: -1 }); + fileExistsOrPick('vsc-webshark.sharkdFullPath', 'sharkd') + .then((_sharkdPath) => { + return vscode.window + .showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + filters: { + 'pcap files': ['pcap', 'cap', 'pcapng'].flatMap((ext) => [ext, ext + '.gz', ext + '.zst', ext + '.lz4']), + }, + openLabel: 'Select pcap file to open...', + }) + .then(async (uris: vscode.Uri[] | undefined) => { + if (uris) { + uris.forEach((uri) => { + console.log(`webshark.openFile got URI=${uri.toString()}`); + const sharkd = new SharkdProcess(_sharkdPath); + sharkd.ready().then((ready) => { + if (ready) { + context.subscriptions.push( + new WebsharkView(undefined, context, treeDataProvider, _onDidChangeSelectedTime, uri, sharkd, activeViews, (r) => { + const idx = activeViews.indexOf(r); + console.log(` openFile dispose called( r idx = ${idx}) activeViews=${activeViews.length}`); + if (idx >= 0) { + activeViews.splice(idx, 1); + } + }) + ); + if (reporter) { + reporter.sendTelemetryEvent('open file', undefined, { err: 0 }); + } + } else { + vscode.window.showErrorMessage(`sharkd connection not ready! Please check setting. Currently used: '${_sharkdPath}'`); + if (reporter) { + reporter.sendTelemetryEvent('open file', undefined, { err: -1 }); + } } - } + }); }); - }); - } - }); - } + } + }); + }) + .catch((err) => { + throw Error(`sharkdPath not valid: ${err}`); + }); }) ); diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..da28721 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,61 @@ +/* -------------------- + * Copyright(C) Matthias Behr, 2024. + */ + +import * as vscode from 'vscode'; +import { fileExists } from './websharkView'; + +export function fileExistsOrPick(settingsName: string, binaryName: string): Promise { + const filePath = vscode.workspace.getConfiguration().get(settingsName); + return new Promise((resolve, reject) => { + if (fileExists(filePath)) { + resolve(filePath); + } else { + vscode.window + .showErrorMessage( + `'${settingsName}' setting not pointing to a file. Please change setting or use file picker. Currently used: '${filePath}'`, + { modal: true }, + 'use file picker...', + 'open settings' + ) + .then((value) => { + switch (value) { + case 'open settings': + vscode.commands.executeCommand('workbench.action.openSettings', settingsName); + reject(`file '${filePath}' does not exist`); + return; + case 'use file picker...': + vscode.window + .showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + openLabel: `Select ${settingsName}`, + }) + .then((uris) => { + if (uris && uris.length > 0) { + // special handling for Mac. If the user selected a folder + // that ends in .app + // check whether + // Contents/MacOS/ exists and use that instead + if (uris[0].fsPath.endsWith('.app')) { + const macPath = `${uris[0].fsPath}/Contents/MacOS/${binaryName}`; + if (fileExists(macPath)) { + vscode.workspace.getConfiguration().update(settingsName, macPath, vscode.ConfigurationTarget.Global); + resolve(macPath); + return; + } + } + vscode.workspace.getConfiguration().update(settingsName, uris[0].fsPath, vscode.ConfigurationTarget.Global); + resolve(uris[0].fsPath); + return; + } else { + reject(`no file selected. file '${filePath}' does not exist`); + } + }); + return; + } + }); + } + }); +} diff --git a/src/websharkView.ts b/src/websharkView.ts index ca89e56..90e7e8a 100644 --- a/src/websharkView.ts +++ b/src/websharkView.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import { spawn, ChildProcess } from 'child_process'; import TelemetryReporter from '@vscode/extension-telemetry'; import { TreeViewProvider, TreeViewNode } from './treeViewProvider'; +import { fileExistsOrPick } from './utils'; let _nextSharkdId = 1; const platformWin32: boolean = process.platform === 'win32'; @@ -190,78 +191,126 @@ export class SharkdProcess implements vscode.Disposable { } export class WebsharkViewSerializer implements vscode.WebviewPanelSerializer { - constructor(private reporter: TelemetryReporter, private treeViewProvider: TreeViewProvider, private _onDidChangeSelectedTime: vscode.EventEmitter, private sharkdPath: string, private context: vscode.ExtensionContext, private activeViews: WebsharkView[], private callOnDispose: (r: WebsharkView) => any) { - - } - async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) { - console.log(`WebsharkView deserializeWebviewPanel called. state='${JSON.stringify(state)}'`); - try { - if ('uri' in state) { - const uri: vscode.Uri = vscode.Uri.parse(state.uri, true); - console.log(`creating WebsharkView for uri=${uri.toString()}`); - - const sharkd = new SharkdProcess(this.sharkdPath); - sharkd.ready().then((ready) => { - if (ready) { - this.context.subscriptions.push(new WebsharkView(webviewPanel, this.context, this.treeViewProvider, this._onDidChangeSelectedTime, uri, sharkd, this.activeViews, this.callOnDispose)); - if (this.reporter) { this.reporter.sendTelemetryEvent("open file", undefined, { 'err': 0 }); } - } else { - vscode.window.showErrorMessage(`sharkd connection not ready! Please check setting. Currently used: '${this.sharkdPath}'`); - if (this.reporter) { this.reporter.sendTelemetryEvent("open file", undefined, { 'err': -1 }); } - } - }); - - } else { console.warn(`deserializeWebviewPanel but no uri within state='${JSON.stringify(state)}'`); } - } catch (err) { - console.warn(`deserializeWebviewPanel got err=${err} with state='${JSON.stringify(state)}'`); - } + constructor( + private reporter: TelemetryReporter, + private treeViewProvider: TreeViewProvider, + private _onDidChangeSelectedTime: vscode.EventEmitter, + private sharkdPath: string, + private context: vscode.ExtensionContext, + private activeViews: WebsharkView[], + private callOnDispose: (r: WebsharkView) => any + ) {} + async deserializeWebviewPanel(webviewPanel: vscode.WebviewPanel, state: any) { + console.log(`WebsharkView deserializeWebviewPanel called. state='${JSON.stringify(state)}'`); + try { + if ('uri' in state) { + const uri: vscode.Uri = vscode.Uri.parse(state.uri, true); + console.log(`creating WebsharkView for uri=${uri.toString()}`); + + const sharkd = new SharkdProcess(this.sharkdPath); + sharkd.ready().then((ready) => { + if (ready) { + this.context.subscriptions.push( + new WebsharkView( + webviewPanel, + this.context, + this.treeViewProvider, + this._onDidChangeSelectedTime, + uri, + sharkd, + this.activeViews, + this.callOnDispose + ) + ); + if (this.reporter) { + this.reporter.sendTelemetryEvent('open file', undefined, { err: 0 }); + } + } else { + vscode.window.showErrorMessage(`sharkd connection not ready! Please check setting. Currently used: '${this.sharkdPath}'`); + if (this.reporter) { + this.reporter.sendTelemetryEvent('open file', undefined, { err: -1 }); + } + } + }); + } else { + console.warn(`deserializeWebviewPanel but no uri within state='${JSON.stringify(state)}'`); + } + } catch (err) { + console.warn(`deserializeWebviewPanel got err=${err} with state='${JSON.stringify(state)}'`); } + } } class WebsharkViewCustomDocument implements vscode.CustomDocument { - constructor(public uri: vscode.Uri) { - console.log(`WebsharkViewCustomDocument(uri=${this.uri.toString()}) called`); - } - dispose() { - console.log(`WebsharkViewCustomDocument dispose(uri=${this.uri.toString()}) called`); - } + constructor(public uri: vscode.Uri) { + console.log(`WebsharkViewCustomDocument(uri=${this.uri.toString()}) called`); + } + dispose() { + console.log(`WebsharkViewCustomDocument dispose(uri=${this.uri.toString()}) called`); + } } export class WebsharkViewReadonlyEditorProvider implements vscode.CustomReadonlyEditorProvider { - constructor(private reporter: TelemetryReporter, private treeViewProvider: TreeViewProvider, private _onDidChangeSelectedTime: vscode.EventEmitter, private context: vscode.ExtensionContext, private activeViews: WebsharkView[], private callOnDispose: (r: WebsharkView) => any) { - } - openCustomDocument(uri: vscode.Uri, openContext: vscode.CustomDocumentOpenContext, token: vscode.CancellationToken): Thenable | WebsharkViewCustomDocument { - console.log(`WebsharkViewReadonlyEditorProvider openCustomDocument(uri=${uri.toString()}, openContext=${JSON.stringify(openContext)}) called`); - // we dont support backupId yet (necessary for readonly at all?) - return new WebsharkViewCustomDocument(uri); - } - resolveCustomEditor(document: WebsharkViewCustomDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken): Thenable | void { - console.log(`WebsharkViewReadonlyEditorProvider resolveCustomEditor(document.uri=${document.uri.toString()}, webviewPanel:${webviewPanel}) called`); - const _wiresharkProfile = (vscode.workspace.getConfiguration().get("vsc-webshark.wiresharkProfile")); - const _sharkdPath = (vscode.workspace.getConfiguration().get("vsc-webshark.sharkdFullPath")); - if (!fileExists(_sharkdPath)) { - vscode.window.showErrorMessage(`sharkdFullPath setting not pointing to a file. Please check setting. Currently used: '${_sharkdPath}'`, - { modal: true }, 'open settings').then((value) => { - switch (value) { - case 'open settings': - vscode.commands.executeCommand('workbench.action.openSettings', "vsc-webshark.sharkdFullPath"); - break; - } - }); - throw Error(`sharkdPath not pointing to a file`); - } else { - const sharkd = new SharkdProcess(_sharkdPath, _wiresharkProfile); - sharkd.ready().then((ready) => { - if (ready) { - this.context.subscriptions.push(new WebsharkView(webviewPanel, this.context, this.treeViewProvider, this._onDidChangeSelectedTime, document.uri, sharkd, this.activeViews, this.callOnDispose)); - if (this.reporter) { this.reporter.sendTelemetryEvent("open file resolve custom editor", { fn: 'resolveCustomEditor' }, { 'err': 0 }); } - } else { - vscode.window.showErrorMessage(`sharkd connection not ready! Please check setting. Currently used: '${_sharkdPath}'`); - if (this.reporter) { this.reporter.sendTelemetryEvent("open file resolve custom editor", { fn: 'resolveCustomEditor' }, { 'err': -1 }); } - } - }); - } - } + constructor( + private reporter: TelemetryReporter, + private treeViewProvider: TreeViewProvider, + private _onDidChangeSelectedTime: vscode.EventEmitter, + private context: vscode.ExtensionContext, + private activeViews: WebsharkView[], + private callOnDispose: (r: WebsharkView) => any + ) {} + openCustomDocument( + uri: vscode.Uri, + openContext: vscode.CustomDocumentOpenContext, + token: vscode.CancellationToken + ): Thenable | WebsharkViewCustomDocument { + console.log( + `WebsharkViewReadonlyEditorProvider openCustomDocument(uri=${uri.toString()}, openContext=${JSON.stringify(openContext)}) called` + ); + // we dont support backupId yet (necessary for readonly at all?) + return new WebsharkViewCustomDocument(uri); + } + resolveCustomEditor( + document: WebsharkViewCustomDocument, + webviewPanel: vscode.WebviewPanel, + token: vscode.CancellationToken + ): Thenable | void { + console.log( + `WebsharkViewReadonlyEditorProvider resolveCustomEditor(document.uri=${document.uri.toString()}, webviewPanel:${webviewPanel}) called` + ); + const _wiresharkProfile = vscode.workspace.getConfiguration().get('vsc-webshark.wiresharkProfile'); + fileExistsOrPick('vsc-webshark.sharkdFullPath', 'sharkd') + .then((filePath) => { + const sharkd = new SharkdProcess(filePath, _wiresharkProfile); + sharkd.ready().then((ready) => { + if (ready) { + this.context.subscriptions.push( + new WebsharkView( + webviewPanel, + this.context, + this.treeViewProvider, + this._onDidChangeSelectedTime, + document.uri, + sharkd, + this.activeViews, + this.callOnDispose + ) + ); + if (this.reporter) { + this.reporter.sendTelemetryEvent('open file resolve custom editor', { fn: 'resolveCustomEditor' }, { err: 0 }); + } + } else { + vscode.window.showErrorMessage(`sharkd connection not ready! Please check setting. Currently used: '${filePath}'`); + if (this.reporter) { + this.reporter.sendTelemetryEvent('open file resolve custom editor', { fn: 'resolveCustomEditor' }, { err: -1 }); + } + } + }); + }) + .catch((err) => { + throw Error(`sharkdPath not valid: ${err}`); + }); + } } interface ResponseData {