1- // src/ragService.ts
2- import { ChildProcess , execSync , exec , spawn } from 'child_process' ;
1+ import { ChildProcess , execSync , exec , spawn } from 'child_process' ;
32import * as vscode from 'vscode' ;
43import * as path from 'path' ;
54import * as fs from 'fs/promises' ;
65import * as net from 'net' ;
76import * as os from 'os' ;
7+ import * as https from 'https' ;
8+ import * as crypto from 'crypto' ;
89
910// Define a variable to store the extension path
1011let EXTENSION_PATH : string = '' ;
@@ -60,6 +61,53 @@ class RagService {
6061 if ( this . state . status !== 'stopped' ) { return ; }
6162
6263 try {
64+ if ( ! isDevMode ) {
65+ const exePath = getPythonScriptPath ( EXTENSION_PATH ) ;
66+ const distPath = path . dirname ( exePath ) ;
67+ await fs . mkdir ( distPath , { recursive : true } ) ;
68+
69+ let shouldDownload = false ;
70+
71+ // Check if rag.exe exists
72+ try {
73+ await fs . access ( exePath , fs . constants . F_OK ) ;
74+
75+ // Download MD5 from GitHub
76+ const remoteMd5 = await this . downloadText ( 'https://github.com/yefansky/CodeReDesign/releases/download/latest/md5.txt' ) ;
77+
78+ // Calculate local rag.exe MD5
79+ const localMd5 = await this . calculateFileMd5 ( exePath ) ;
80+
81+ // Compare MD5s
82+ if ( remoteMd5 . trim ( ) . toLowerCase ( ) !== localMd5 . toLowerCase ( ) ) {
83+ shouldDownload = true ;
84+ vscode . window . showInformationMessage ( 'rag.exe MD5 mismatch, downloading new version...' ) ;
85+ }
86+ } catch ( err ) {
87+ // rag.exe doesn't exist
88+ shouldDownload = true ;
89+ vscode . window . showInformationMessage ( 'rag.exe not found, downloading...' ) ;
90+ }
91+
92+ if ( shouldDownload ) {
93+ // Kill any running rag.exe process
94+ await this . killRagExeProcess ( ) ;
95+
96+ // Download new rag.exe
97+ await this . downloadFile (
98+ 'https://github.com/yefansky/CodeReDesign/releases/download/latest/rag.exe' ,
99+ exePath
100+ ) ;
101+
102+ // Verify downloaded file's MD5
103+ const newMd5 = await this . calculateFileMd5 ( exePath ) ;
104+ const remoteMd5 = await this . downloadText ( 'https://github.com/yefansky/CodeReDesign/releases/download/latest/md5.txt' ) ;
105+ if ( newMd5 . toLowerCase ( ) !== remoteMd5 . trim ( ) . toLowerCase ( ) ) {
106+ throw new Error ( 'Downloaded rag.exe MD5 verification failed' ) ;
107+ }
108+ }
109+ }
110+
63111 //await this.acquireLock();
64112 await this . ensurePortAvailable ( ) ;
65113
@@ -74,6 +122,94 @@ class RagService {
74122 }
75123 }
76124
125+ private async downloadText ( url : string ) : Promise < string > {
126+ return new Promise ( ( resolve , reject ) => {
127+ https . get ( url , ( res ) => {
128+ let data = '' ;
129+ res . on ( 'data' , ( chunk ) => { data += chunk ; } ) ;
130+ res . on ( 'end' , ( ) => {
131+ if ( res . statusCode !== 200 ) {
132+ reject ( new Error ( `Failed to download text from ${ url } : Status ${ res . statusCode } ` ) ) ;
133+ } else {
134+ resolve ( data ) ;
135+ }
136+ } ) ;
137+ } ) . on ( 'error' , ( err ) => {
138+ reject ( new Error ( `Failed to download text from ${ url } : ${ err . message } ` ) ) ;
139+ } ) ;
140+ } ) ;
141+ }
142+
143+ private async downloadFile ( url : string , dest : string ) : Promise < void > {
144+ return new Promise ( ( resolve , reject ) => {
145+ const file = require ( 'fs' ) . createWriteStream ( dest ) ;
146+ https . get ( url , ( res ) => {
147+ if ( res . statusCode !== 200 ) {
148+ reject ( new Error ( `Failed to download file from ${ url } : Status ${ res . statusCode } ` ) ) ;
149+ return ;
150+ }
151+ res . pipe ( file ) ;
152+ file . on ( 'finish' , ( ) => {
153+ file . close ( ) ;
154+ resolve ( ) ;
155+ } ) ;
156+ } ) . on ( 'error' , ( err ) => {
157+ require ( 'fs' ) . unlink ( dest , ( ) => { } ) ; // Clean up partial download
158+ reject ( new Error ( `Failed to download file from ${ url } : ${ err . message } ` ) ) ;
159+ } ) ;
160+ } ) ;
161+ }
162+
163+ private async calculateFileMd5 ( filePath : string ) : Promise < string > {
164+ const fileBuffer = await fs . readFile ( filePath ) ;
165+ return crypto . createHash ( 'md5' ) . update ( fileBuffer ) . digest ( 'hex' ) ;
166+ }
167+
168+ private async killRagExeProcess ( ) : Promise < void > {
169+ if ( process . platform !== 'win32' ) {
170+ return ; // Only implemented for Windows as rag.exe is Windows-specific
171+ }
172+
173+ return new Promise ( ( resolve , reject ) => {
174+ try {
175+ const command = `tasklist | findstr "rag.exe"` ;
176+ exec ( command , { shell : 'cmd.exe' , encoding : 'utf8' } , ( error , stdout ) => {
177+ if ( error && ! stdout ) {
178+ console . log ( 'No rag.exe process found' ) ;
179+ return resolve ( ) ;
180+ }
181+
182+ const pids = stdout
183+ . trim ( )
184+ . split ( '\n' )
185+ . map ( line => {
186+ const parts = line . trim ( ) . split ( / \s + / ) ;
187+ return parts [ 1 ] ; // PID is in the second column
188+ } )
189+ . filter ( pid => pid && / ^ \d + $ / . test ( pid ) ) ;
190+
191+ if ( pids . length === 0 ) {
192+ console . log ( 'No valid rag.exe PIDs found' ) ;
193+ return resolve ( ) ;
194+ }
195+
196+ console . log ( `Terminating rag.exe PIDs: ${ pids . join ( ', ' ) } ` ) ;
197+ const killCommand = `taskkill /F /PID ${ pids . join ( ' ' ) } ` ;
198+ exec ( killCommand , { shell : 'cmd.exe' } , ( killError ) => {
199+ if ( killError ) {
200+ console . error ( `Failed to terminate rag.exe processes: ${ killError . message } ` ) ;
201+ return reject ( new Error ( `Failed to terminate rag.exe processes: ${ killError . message } ` ) ) ;
202+ }
203+ resolve ( ) ;
204+ } ) ;
205+ } ) ;
206+ } catch ( err ) {
207+ console . error ( `Error killing rag.exe process: ${ ( err as Error ) . message } ` ) ;
208+ reject ( new Error ( `Failed to kill rag.exe process: ${ ( err as Error ) . message } ` ) ) ;
209+ }
210+ } ) ;
211+ }
212+
77213 public async stop ( ) : Promise < void > {
78214 if ( this . state . process ) {
79215 this . state . process . kill ( 'SIGTERM' ) ;
@@ -284,8 +420,10 @@ class RagService {
284420 const scriptPath = getPythonScriptPath ( EXTENSION_PATH ) ;
285421 const isExe = scriptPath . endsWith ( '.exe' ) ;
286422
287- if ( ! fs . access ( scriptPath ) ) {
288- throw new Error ( `Script or executable not found: ${ scriptPath } ` ) ;
423+ try {
424+ fs . access ( scriptPath , fs . constants . X_OK ) ;
425+ } catch ( err ) {
426+ throw new Error ( `Script or executable not found or not executable: ${ scriptPath } ` ) ;
289427 }
290428
291429 if ( isExe ) {
0 commit comments