11import * as xdebug from './xdebugConnection'
2- import * as semver from 'semver'
32import * as net from 'net'
3+ import * as CP from 'child_process'
4+ import { promisify } from 'util'
5+ import { supportedEngine } from './xdebugUtils'
6+ import { DOMParser } from '@xmldom/xmldom'
47
58export class ControlSocket {
69 /**
@@ -16,36 +19,36 @@ export class ControlSocket {
1619 * @returns Returns true if the current platform and xdebug version are supporting Xdebug control socket.
1720 */
1821 supportedInitPacket ( initPacket : xdebug . InitPacket ) : boolean {
19- return (
20- this . supportedPlatform ( ) &&
21- initPacket . engineName === 'Xdebug' &&
22- semver . valid ( initPacket . engineVersion , { loose : true } ) !== null &&
23- ( semver . gte ( initPacket . engineVersion , '3.5.0' , { loose : true } ) ||
24- initPacket . engineVersion . startsWith ( '3.5.0' ) )
25- )
22+ return this . supportedPlatform ( ) && supportedEngine ( initPacket , '3.5.0' )
2623 }
2724
2825 /**
29- *
26+ * Request the pause control socket command
3027 * @param ctrlSocket Control socket full path
3128 * @returns
3229 */
3330 async requestPause ( ctrlSocket : string ) : Promise < void > {
34- let retval
31+ await this . executeCtrlCmd ( ctrlSocket , 'pause' )
32+ }
33+
34+ async requestPS ( ctrlSocket : string ) : Promise < ControlPS > {
35+ const xml = await this . executeCtrlCmd ( ctrlSocket , 'ps' )
36+ const parser = new DOMParser ( )
37+ const document = < unknown > parser . parseFromString ( xml , 'application/xml' )
38+ return new ControlPS ( < XMLDocument > document )
39+ }
40+
41+ private async executeCtrlCmd ( ctrlSocket : string , cmd : string ) : Promise < string > {
42+ let rawCtrlSocket : string
3543 if ( process . platform === 'linux' ) {
36- retval = await this . executeCtrlCmd ( `\0${ ctrlSocket } ` , 'pause' )
44+ rawCtrlSocket = `\0${ ctrlSocket } `
3745 } else if ( process . platform === 'win32' ) {
38- retval = await this . executeCtrlCmd ( `\\\\.\\pipe\\${ ctrlSocket } ` , 'pause' )
46+ rawCtrlSocket = `\\\\.\\pipe\\${ ctrlSocket } `
3947 } else {
4048 throw new Error ( 'Invalid platform for Xdebug control socket' )
4149 }
42- retval
43- return
44- }
45-
46- private async executeCtrlCmd ( ctrlSocket : string , cmd : string ) : Promise < string > {
4750 return new Promise < string > ( ( resolve , reject ) => {
48- const s = net . createConnection ( ctrlSocket , ( ) => {
51+ const s = net . createConnection ( rawCtrlSocket , ( ) => {
4952 s . end ( `${ cmd } \0` )
5053 } )
5154 s . setTimeout ( 3000 )
@@ -69,4 +72,82 @@ export class ControlSocket {
6972 return
7073 } )
7174 }
75+
76+ async listControlSockets ( ) : Promise < XdebugRunningProcess [ ] > {
77+ let retval :XdebugRunningProcess [ ]
78+ if ( process . platform === 'linux' ) {
79+ // TODO
80+ throw new Error ( 'Invalid platform for Xdebug control socket' )
81+ } else if ( process . platform === 'win32' ) {
82+ retval = await this . listControlSocketsWin ( )
83+ } else {
84+ throw new Error ( 'Invalid platform for Xdebug control socket' )
85+ }
86+
87+ const retval2 = Promise . all (
88+ retval . map ( async v => {
89+ try {
90+ v . ps = await this . requestPS ( v . ctrlSocket )
91+ } catch {
92+ // ignore
93+ }
94+ return v
95+ } )
96+ )
97+ return retval2
98+ }
99+
100+ private async listControlSocketsWin ( ) : Promise < XdebugRunningProcess [ ] > {
101+ const exec = promisify ( CP . exec )
102+ try {
103+ const ret = await exec ( 'cmd /C "dir \\\\.\\pipe\\\\xdebug-ctrl* /b"' )
104+ const lines = ret . stdout . split ( '\r\n' )
105+
106+ const retval = lines
107+ . filter ( v => v . length != 0 )
108+ . map < XdebugRunningProcess > ( v => < XdebugRunningProcess > { ctrlSocket : v } )
109+
110+ return retval
111+
112+ } catch ( err ) {
113+ if ( err instanceof Error && ( < ExecError > err ) . stderr == 'File Not Found\r\n' ) {
114+ return [ ]
115+ }
116+ throw err
117+ }
118+ }
119+ }
120+
121+ interface ExecError extends Error {
122+ stderr : string
123+ }
124+
125+ export interface XdebugRunningProcess {
126+ readonly ctrlSocket : string
127+ ps : ControlPS
128+ // todo
129+ }
130+
131+ export class ControlPS {
132+ /** The file that was requested as a file:// URI */
133+ fileUri : string
134+ /** the version of Xdebug */
135+ engineVersion : string
136+ /** the name of the engine */
137+ engineName : string
138+ /** the internal PID */
139+ pid : string
140+ /** memory consumption */
141+ memory : number
142+ /**
143+ * @param {XMLDocument } document - An XML document to read from
144+ */
145+ constructor ( document : XMLDocument ) {
146+ const documentElement = < Element > document . documentElement . firstChild
147+ this . fileUri = documentElement . getElementsByTagName ( 'fileuri' ) . item ( 0 ) ?. textContent ?? ''
148+ this . engineVersion = documentElement . getElementsByTagName ( 'engine' ) . item ( 0 ) ?. getAttribute ( 'version' ) ?? ''
149+ this . engineName = documentElement . getElementsByTagName ( 'engine' ) . item ( 0 ) ?. textContent ?? ''
150+ this . pid = documentElement . getElementsByTagName ( 'pid' ) . item ( 0 ) ?. textContent ?? ''
151+ this . memory = parseInt ( documentElement . getElementsByTagName ( 'memory' ) . item ( 0 ) ?. textContent ?? '0' )
152+ }
72153}
0 commit comments