11// src/ragService.ts
2- import { ChildProcess , execSync , spawn } from 'child_process' ;
2+ import { ChildProcess , execSync , exec , spawn } from 'child_process' ;
33import * as vscode from 'vscode' ;
44import * as path from 'path' ;
55import * as fs from 'fs/promises' ;
@@ -10,24 +10,23 @@ import * as os from 'os';
1010let EXTENSION_PATH : string = '' ;
1111
1212const isDevMode = __dirname . includes ( 'dist' ) ;
13- // 动态设置 Python 脚本或 EXE 路径
13+ // Dynamically set Python script or EXE path
1414const getPythonScriptPath = ( extensionPath : string ) => {
1515 if ( isDevMode ) {
16- return path . join ( extensionPath , 'src' , 'python' , 'rag.py' ) ; // 开发模式:直接运行 Python 脚本
16+ return path . join ( extensionPath , 'src' , 'python' , 'rag.py' ) ; // Development mode: run Python script directly
1717 } else {
18- return path . join ( extensionPath , 'dist' , 'rag.exe' ) ; // 发布模式:运行 EXE
18+ return path . join ( extensionPath , 'dist' , 'rag.exe' ) ; // Production mode: run EXE
1919 }
2020} ;
2121
22- // 配置常量 (use a function to resolve paths dynamically)
22+ // Configuration constants (use a function to resolve paths dynamically)
2323export const CONFIG = {
2424 STORAGE_PATH : path . join ( os . homedir ( ) , 'CodeReDesignMemory' , 'rag_storage' ) ,
2525 PORT : 7111 ,
26- LOCK_FILE : 'rag.lock' ,
27- PYTHON_SCRIPT : getPythonScriptPath ( EXTENSION_PATH )
28- } ;
26+ LOCK_FILE : 'rag.lock'
27+ } ;
2928
30- // 类型定义
29+ // Type definitions
3130type ServerStatus = 'stopped' | 'starting' | 'running' | 'error' ;
3231
3332interface ServerState {
@@ -53,7 +52,7 @@ class RagService {
5352
5453 private initializeStorage ( ) : void {
5554 fs . mkdir ( CONFIG . STORAGE_PATH , { recursive : true } ) . catch ( err => {
56- vscode . window . showErrorMessage ( `创建存储目录失败 : ${ err } ` ) ;
55+ vscode . window . showErrorMessage ( `Failed to create storage directory : ${ err } ` ) ;
5756 } ) ;
5857 }
5958
@@ -71,7 +70,7 @@ class RagService {
7170 await this . waitForStartup ( ) ;
7271
7372 } catch ( err ) {
74- await this . cleanup ( `启动失败 : ${ err } ` ) ;
73+ await this . cleanup ( `Startup failed : ${ err } ` ) ;
7574 }
7675 }
7776
@@ -91,74 +90,176 @@ class RagService {
9190 this . state . lockFileHandle = handle ;
9291 } catch ( err ) {
9392 if ( ( err as NodeJS . ErrnoException ) . code === 'EEXIST' ) {
94- throw new Error ( '已有服务进程在运行 ' ) ;
93+ throw new Error ( 'Another service process is already running ' ) ;
9594 }
9695 throw err ;
9796 }
9897 }
9998
100- private async ensurePortAvailable ( ) : Promise < void > {
99+ private async checkPortInUse ( port : number ) : Promise < boolean > {
101100 return new Promise ( ( resolve , reject ) => {
102- const server = net . createServer ( ) ;
103- server . unref ( ) ;
104-
105- server . on ( 'error' , ( err : NodeJS . ErrnoException ) => {
106- if ( err . code === 'EADDRINUSE' ) {
107- this . killProcessOnPort ( CONFIG . PORT )
108- . then ( resolve )
109- . catch ( reject ) ;
110- } else {
111- reject ( err ) ;
101+ try {
102+ let command : string ;
103+ let shell : string ;
104+
105+ if ( process . platform === 'win32' ) {
106+ // 使用 netstat 检查端口,查找 LISTENING 状态
107+ command = `netstat -aon | findstr /R "^.*:${ port } \\s.*LISTENING.*$"` ;
108+ shell = 'cmd.exe' ;
109+ } else {
110+ command = `lsof -i :${ port } -t` ;
111+ shell = '/bin/sh' ;
112+ }
113+
114+ exec ( command , { shell, encoding : 'utf8' } , ( error , stdout ) => {
115+ if ( error && ! stdout ) {
116+ console . log ( `Port ${ port } is not in use (command check)` ) ;
117+ resolve ( false ) ;
118+ } else if ( stdout . trim ( ) ) {
119+ console . log ( `Port ${ port } is in use (command check): ${ stdout . trim ( ) } ` ) ;
120+ resolve ( true ) ;
121+ } else {
122+ resolve ( false ) ;
123+ }
124+ } ) ;
125+ } catch ( err ) {
126+ console . error ( `Failed to check port ${ port } : ${ ( err as Error ) . message } ` ) ;
127+ reject ( new Error ( `Failed to check port ${ port } : ${ ( err as Error ) . message } ` ) ) ;
112128 }
113- } ) ;
129+ } ) ;
130+ }
114131
115- server . listen ( CONFIG . PORT , ( ) => {
116- server . close ( ( ) => resolve ( ) ) ;
117- } ) ;
132+ private async ensurePortAvailable ( ) : Promise < void > {
133+ console . log ( `Checking if port ${ CONFIG . PORT } is available...` ) ;
134+
135+ // 主动检查端口是否被占用
136+ const isPortInUse = await this . checkPortInUse ( CONFIG . PORT ) ;
137+ if ( isPortInUse ) {
138+ console . log ( `Port ${ CONFIG . PORT } is in use, attempting to free it...` ) ;
139+ await this . killProcessOnPort ( CONFIG . PORT ) ;
140+ }
141+
142+ // 使用 net.createServer 进行二次验证
143+ return new Promise ( ( resolve , reject ) => {
144+ const server = net . createServer ( ) ;
145+ server . unref ( ) ;
146+
147+ server . on ( 'error' , ( err : NodeJS . ErrnoException ) => {
148+ console . log ( `net.createServer error: ${ err . message } ` ) ;
149+ if ( err . code === 'EADDRINUSE' ) {
150+ console . log ( `Port ${ CONFIG . PORT } is still in use, attempting to free it again...` ) ;
151+ this . killProcessOnPort ( CONFIG . PORT )
152+ . then ( resolve )
153+ . catch ( reject ) ;
154+ } else {
155+ reject ( new Error ( `Failed to check port ${ CONFIG . PORT } : ${ err . message } ` ) ) ;
156+ }
157+ } ) ;
158+
159+ server . listen ( CONFIG . PORT , '0.0.0.0' , ( ) => {
160+ console . log ( `Port ${ CONFIG . PORT } is available via net.createServer` ) ;
161+ server . close ( ( ) => resolve ( ) ) ;
162+ } ) ;
118163 } ) ;
119164 }
120165
121166 private killProcessOnPort ( port : number ) : Promise < void > {
167+ if ( ! Number . isInteger ( port ) || port < 1 || port > 65535 ) {
168+ throw new Error ( `Invalid port number: ${ port } ` ) ;
169+ }
170+
122171 return new Promise ( ( resolve , reject ) => {
123- try {
124- const command = process . platform === 'win32'
125- ? `netstat -ano | findstr :${ port } && FOR /F "tokens=5" %p IN ('netstat -ano ^| findstr :${ port } ') DO taskkill /F /PID %p`
126- : `lsof -i :${ port } -t | xargs kill -9` ;
127-
128- // 修复类型错误
129- execSync ( command , {
130- shell : process . platform === 'win32' ? 'cmd.exe' : '/bin/sh' ,
131- encoding : 'utf-8'
132- } ) ;
133-
134- setTimeout ( resolve , 2000 ) ;
135- } catch ( err ) {
136- reject ( new Error ( `无法释放端口 ${ port } ` ) ) ;
137- }
172+ try {
173+ let command : string ;
174+ let shell : string ;
175+
176+ if ( process . platform === 'win32' ) {
177+ command = `netstat -aon | findstr /R "TCP.*:${ port } .*LISTENING"` ;
178+ shell = 'cmd.exe' ;
179+ } else {
180+ command = `lsof -i :${ port } -t` ;
181+ shell = '/bin/sh' ;
182+ }
183+
184+ exec ( command , { shell, encoding : 'utf8' } , ( error , stdout ) => {
185+ if ( error && ! stdout ) {
186+ console . log ( `No process found on port ${ port } ` ) ;
187+ return resolve ( ) ;
188+ }
189+
190+ const pids = stdout
191+ . trim ( )
192+ . split ( '\n' )
193+ . map ( line => {
194+ const parts = line . trim ( ) . split ( / \s + / ) ;
195+ return parts [ parts . length - 1 ] ; // PID 在最后一列
196+ } )
197+ . filter ( pid => pid && / ^ \d + $ / . test ( pid ) ) ; // 确保是数字
198+
199+ if ( pids . length === 0 ) {
200+ console . log ( `No valid PIDs found on port ${ port } ` ) ;
201+ return resolve ( ) ;
202+ }
203+
204+ console . log ( `Terminating PIDs on port ${ port } : ${ pids . join ( ', ' ) } ` ) ;
205+ const killCommand = process . platform === 'win32'
206+ ? `taskkill /F /PID ${ pids . join ( ' ' ) } `
207+ : `kill -TERM ${ pids . join ( ' ' ) } ` ;
208+
209+ exec ( killCommand , { shell } , ( killError ) => {
210+ if ( killError ) {
211+ console . error ( `Failed to terminate processes: ${ killError . message } ` ) ;
212+ return reject ( new Error ( `Failed to terminate processes on port ${ port } : ${ killError . message } ` ) ) ;
213+ }
214+
215+ // 重试检查端口是否释放
216+ let retries = 3 ;
217+ const checkPort = ( ) => {
218+ exec ( command , { shell, encoding : 'utf8' } , ( checkError , checkStdout ) => {
219+ if ( checkStdout . trim ( ) ) {
220+ console . log ( `Port ${ port } still in use, retries left: ${ retries } ` ) ;
221+ if ( -- retries > 0 ) {
222+ setTimeout ( checkPort , 1000 ) ;
223+ } else {
224+ reject ( new Error ( `Port ${ port } still in use after termination` ) ) ;
225+ }
226+ } else {
227+ console . log ( `Port ${ port } successfully released` ) ;
228+ resolve ( ) ;
229+ }
230+ } ) ;
231+ } ;
232+ setTimeout ( checkPort , 1000 ) ;
233+ } ) ;
234+ } ) ;
235+ } catch ( err ) {
236+ console . error ( `Error freeing port ${ port } : ${ ( err as Error ) . message } ` ) ;
237+ reject ( new Error ( `Failed to free port ${ port } : ${ ( err as Error ) . message } ` ) ) ;
238+ }
138239 } ) ;
139240 }
140241
141242 private findWindowsPython ( ) : string {
142243 try {
143- // 获取所有Python路径并解析
244+ // Get all Python paths and parse
144245 const output = execSync ( 'where python' , {
145246 shell : 'cmd.exe' ,
146247 encoding : 'utf-8'
147248 } ) ;
148249
149- // 清洗并分割路径
250+ // Clean and split paths
150251 const paths = output
151- . replace ( / \r / g, '' ) // 去除回车符
252+ . replace ( / \r / g, '' ) // Remove carriage returns
152253 . split ( '\n' )
153254 . map ( p => p . trim ( ) )
154255 . filter ( p => p . length > 0 ) ;
155256
156- // 验证第一个有效路径
257+ // Verify the first valid path
157258 if ( paths . length === 0 ) {
158- throw new Error ( '未找到Python路径 ' ) ;
259+ throw new Error ( 'No Python path found ' ) ;
159260 }
160261
161- // 优先选择带空格的路径用双引号包裹
262+ // Prefer paths with spaces wrapped in quotes
162263 const validPath = paths . find ( p => {
163264 try {
164265 fs . access ( p , fs . constants . X_OK ) ;
@@ -169,26 +270,26 @@ class RagService {
169270 } ) ;
170271
171272 return validPath
172- ? `"${ validPath } "` // 处理路径中的空格
173- : paths [ 0 ] ; // 最后兜底方案
273+ ? `"${ validPath } "` // Handle spaces in path
274+ : paths [ 0 ] ; // Fallback
174275
175276 } catch ( error ) {
176- // 回退方案
177- vscode . window . showWarningMessage ( 'Python自动检测失败,使用默认路径 ' ) ;
277+ // Fallback scheme
278+ vscode . window . showWarningMessage ( 'Python detection failed, using default path ' ) ;
178279 return 'python.exe' ;
179280 }
180281 }
181282
182283 private startPythonProcess ( ) : ChildProcess {
183- const scriptPath = CONFIG . PYTHON_SCRIPT ;
284+ const scriptPath = getPythonScriptPath ( EXTENSION_PATH ) ;
184285 const isExe = scriptPath . endsWith ( '.exe' ) ;
185286
186287 if ( ! fs . access ( scriptPath ) ) {
187- throw new Error ( `未找到脚本或可执行文件 : ${ scriptPath } ` ) ;
288+ throw new Error ( `Script or executable not found : ${ scriptPath } ` ) ;
188289 }
189290
190291 if ( isExe ) {
191- // 直接运行 EXE
292+ // Run EXE directly
192293 return spawn ( scriptPath , [
193294 `--port=${ CONFIG . PORT } ` ,
194295 `--storage_path=${ CONFIG . STORAGE_PATH } `
@@ -198,7 +299,7 @@ class RagService {
198299 shell : true
199300 } ) ;
200301 } else {
201- // 运行 Python 脚本
302+ // Run Python script
202303 const pythonPath = process . platform === 'win32'
203304 ? this . findWindowsPython ( )
204305 : this . findUnixPython ( ) ;
@@ -217,7 +318,7 @@ class RagService {
217318
218319 private findUnixPython ( ) : string {
219320 try {
220- // 精确获取python3路径
321+ // Precisely get python3 path
221322 return execSync ( 'which python3' , {
222323 shell : '/bin/sh' ,
223324 encoding : 'utf-8'
@@ -234,26 +335,25 @@ class RagService {
234335 process . stderr ?. on ( 'data' , ( data : Buffer ) => {
235336 if ( data . includes ( 'Uvicorn running' ) ) {
236337 this . state . status = 'running' ;
237- vscode . window . showInformationMessage ( 'RAG 服务已启动 ' ) ;
338+ vscode . window . showInformationMessage ( 'RAG service started ' ) ;
238339 }
239340 } ) ;
240341
241342 process . stderr ?. on ( 'data' , ( data : Buffer ) => {
242- //this.state.status = 'error';
243343 console . error ( `[Python Error] ${ data } ` ) ;
244344 } ) ;
245345
246346 process . on ( 'exit' , code => {
247347 if ( code !== 0 ) {
248- this . cleanup ( `服务异常退出,代码 : ${ code } ` ) ;
348+ this . cleanup ( `Service exited abnormally with code : ${ code } ` ) ;
249349 }
250350 } ) ;
251351 }
252352
253353 private async waitForStartup ( ) : Promise < void > {
254354 return new Promise ( ( resolve , reject ) => {
255355 const timeout = setTimeout ( ( ) => {
256- reject ( new Error ( '服务启动超时 ' ) ) ;
356+ reject ( new Error ( 'Service startup timed out ' ) ) ;
257357 } , 15000 ) ;
258358
259359 const checkInterval = setInterval ( ( ) => {
@@ -281,7 +381,7 @@ class RagService {
281381 }
282382}
283383
284- // 插件集成
384+ // Plugin integration
285385export const ragService = RagService . getInstance ( ) ;
286386
287387export async function activate ( context : vscode . ExtensionContext ) {
0 commit comments