From 7cab1b48ae89cf13dc407858c7b69141b6f0dbbe Mon Sep 17 00:00:00 2001 From: weike Date: Wed, 29 May 2024 14:19:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20=E5=AF=BC=E5=85=A5utils?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=87=BA=E9=94=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../deploy/deployWindowMain.ts | 30 +- .../issue/electron-main/deployMainService.ts | 363 +++++++++++++++++- src/vs/platform/issue/electron-main/utils.ts | 8 +- 3 files changed, 387 insertions(+), 14 deletions(-) diff --git a/src/vs/code/electron-sandbox/deploy/deployWindowMain.ts b/src/vs/code/electron-sandbox/deploy/deployWindowMain.ts index 9df92ad7..0115d326 100644 --- a/src/vs/code/electron-sandbox/deploy/deployWindowMain.ts +++ b/src/vs/code/electron-sandbox/deploy/deployWindowMain.ts @@ -28,7 +28,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { mainWindow } from 'vs/base/browser/window'; import { ThemeIcon } from 'vs/base/common/themables'; import { Codicon } from 'vs/base/common/codicons'; -import { get_g_index_url, configDir } from 'vs/platform/issue/electron-main/utils'; +import * as utils from 'vs/platform/issue/electron-main/utils'; import { addDisposableListener, EventType, getWindow, getWindowId, hide, show } from 'vs/base/browser/dom'; import { isDisposable } from 'vs/base/common/lifecycle'; @@ -239,8 +239,8 @@ class deployWindow { preElement!.innerHTML = ""; const messageData = { - url: get_g_index_url(), - dst: configDir() + url: utils.get_g_index_url(), + dst: utils.configDir() } //联网检查是否有合适的文件 ipcRenderer.send("kylinide.installWizard.checkInstall", messageData); @@ -305,9 +305,12 @@ class deployWindow { dTpreElement!.innerHTML = ''; let dTformattedDoc = ''; - let progressViews = document.getElementsByClassName("progress-view") as HTMLCollectionOf; - progressViews[0].value = (this.progressNum / this.extInstallState.size) * 100; - document.getElementsByClassName("progressinfo")[0].innerHTML = "安装进度:" + ((this.progressNum / this.extInstallState.size) * 100).toFixed(2) + "%"; + console.log("progressNum:" + this.progressNum + " extInstallState.size:" + this.extInstallState.size) + if (this.extInstallState.size > 0) { + let progressViews = document.getElementsByClassName("progress-view") as HTMLCollectionOf; + progressViews[0].value = (this.progressNum / this.extInstallState.size) * 100; + document.getElementsByClassName("progressinfo")[0].innerHTML = "安装进度:" + ((this.progressNum / this.extInstallState.size) * 100).toFixed(2) + "%"; + } for (const [key, value] of this.extInstallState) { if (key === undefined) continue; @@ -527,7 +530,7 @@ class deployWindow { console.log("onlineInstallArrayIndex:" + this.onlineInstallTabArrayIndex); if (this.onlineInstallTabArray[this.onlineInstallTabArrayIndex] === "onlineInstallTab1") { //获取radio的值 - + console.log("onlineInstallTab1"); let select = "local"; if (this.onlineUpdateConfigRadio && this.onlineUpdateConfigRadio instanceof HTMLInputElement && this.onlineUpdateConfigRadio.checked) { select = "update" @@ -542,9 +545,11 @@ class deployWindow { type: "configGetValue", class: "install" } + console.log("onlineInstallTab2"); ipcRenderer.send('kylinide.installWizard.init', data); } else if (this.onlineInstallTabArray[this.onlineInstallTabArrayIndex] === "onlineInstallTab3") { const checkboxForm = document.getElementById("checkboxForm"); + console.log("onlineInstallTab3"); // 获取所有选中的复选框 let selectedCheckboxes = checkboxForm!.querySelectorAll('input[type="checkbox"]:checked'); let checkSelectedValues = []; @@ -622,10 +627,12 @@ class deployWindow { ipcRenderer.send('kylinide.installWizard.init', { type: "writeJson", }); } else if (this.onlineInstallTabArray[this.onlineInstallTabArrayIndex] === "onlineInstallTab4") { ipcRenderer.send('kylinide.installWizard.ok'); + console.log("onlineInstallTab4"); this.removeAllListeners(); return; } if (this.onlineInstallTabArrayIndex < this.onlineInstallTabArray.length - 1) { + console.log("this.onlineInstallTabArrayIndex < this.onlineInstallTabArray.length - 1"); if (this.onlineInstallTabArrayIndex == 0) { console.log("在线安装"); ipcRenderer.send("kylinide.installWizard.init", { type: "httpVerify" }); @@ -645,6 +652,7 @@ class deployWindow { // } if (this.onlineInstallTabArray[this.onlineInstallTabArrayIndex] === "onlineInstallTab4") { + console.log("onlineInstallTab4 last"); this.prevBtn!.disabled = true; this.nextBtn!.disabled = true; this.nextBtn!.innerHTML = "确定"; @@ -720,12 +728,18 @@ class deployWindow { console.log('click nextStep'); this.nextPrev(1); }); + document.getElementById('cancel')?.addEventListener('click', () => { + console.log('cancel'); + // ipcRenderer.removeAllListeners('kylinide.installWizard.msg'); + ipcRenderer.send('kylinide.installWizard.msg.process', { type: "cancel" }); + }); /** * 安装界面:安装详情显示/隐藏切换按钮 */ document.getElementById("switchInstallOutput")?.addEventListener("click", () => { // 切换显示和隐藏 + console.log("switchInstallOutput clicked"); const logPreElement = document.getElementById("installLogPre"); const installPreElement = document.getElementById("detailInstallPre"); const extLogPreElement = document.getElementById("extInstallLogPre"); @@ -1086,7 +1100,7 @@ class deployWindow { extList: this.extList, depList: this.depList, script: obj, - dst: configDir() + dst: utils.configDir() }; ipcRenderer.send("kylinide.installWizard.msg.process", installMsg); } diff --git a/src/vs/platform/issue/electron-main/deployMainService.ts b/src/vs/platform/issue/electron-main/deployMainService.ts index 5b7c753e..3cb1da88 100644 --- a/src/vs/platform/issue/electron-main/deployMainService.ts +++ b/src/vs/platform/issue/electron-main/deployMainService.ts @@ -109,6 +109,23 @@ export class DeployMainService implements IDeployMainService { depList: string[] = []; extList: string[] = []; lastLogPosition: number = 0; + //文件监听; + installFsWatcher: fs.FSWatcher | null = null; + extlogFsWatcher: fs.FSWatcher | null = null; + pkglogFsWatcher: fs.FSWatcher | null = null; + //执行安装脚本的进程号; + installProcessId: ChildProcess | null = null; + cancelFlag = false; + + flagPkexec = 0; + maxBackupFiles = 7; + lastExtLogPosition = 0; + + logFile0 = ""; + resultFile = ""; + extLogFile0 = ""; + + controller = new AbortController(); constructor( private userEnv: IProcessEnvironment, @@ -133,6 +150,10 @@ export class DeployMainService implements IDeployMainService { this.localConfigDir = installUtils.localConfigPath(); this.installConfig = installUtils.installConfigDirPre();//installconfig this.updateConfigDir = installUtils.installConfigDirPre() + "/" + installUtils.getDateDir(); + this.logFile0 = this.installConfig + '/log/install.1.log'; + this.resultFile = this.installConfig + '/resultFile'; + this.extLogFile0 = this.installConfig + '/log/extInstall.1.log'; + this.registerListeners(); } @@ -155,12 +176,348 @@ export class DeployMainService implements IDeployMainService { this.closeWindow(); }); + validatedIpcMain.on('kylinide.installWizard.msg.process', async (event, msg) => { + if (msg.type === 'installExt') { + + const logFile0 = this.installConfig + '/log/extInstall.1.log'; + + if (!fs.existsSync(logFile0)) { + fs.mkdirSync(this.installConfig + "/log", { recursive: true }); + } + + try { + const installItem = this.extDownloadPath + msg.downloadDone; + const installCommand = "kylin-code --install-extension " + installItem; + if (this.installQueue.length === 0) { + this.installQueue.push({ installCommand, msg }); + await this.executeNextInstall(); + } + else { + this.installQueue.push({ installCommand, msg }); + } + + } catch (error) { + console.error(`执行出错: ${error.message}`); + } + } + else if (msg.type === 'installPkg') { + console.log("kylinide.installWizard.msg.process:installPkg"); + const extlogFile = this.installConfig + '/log/extInstall.log'; + this.backupLogFile(extlogFile) + const extlogFile0 = this.installConfig + '/log/extInstall.1.log'; + fs.writeFileSync(extlogFile0, ''); + + if (this.depList.length === 0) { + const startDownloadExtMsg = { + type: "extDownloadInit", + } + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", startDownloadExtMsg); + this.lastExtLogPosition = 0; + + fs.watch(this.extLogFile0, (eventType, filename) => { + + if (eventType === 'change') { + // 获取文件的当前大小 + const stats = fs.statSync(this.extLogFile0); + const currentSize = stats.size; + + // 计算更新部分的大小 + const updateSize = currentSize - this.lastExtLogPosition; + if (updateSize <= 0) return; + // 读取更新部分的内容 + const buffer = Buffer.alloc(updateSize); + const fileDescriptor = fs.openSync(this.extLogFile0, 'r'); + fs.readSync(fileDescriptor, buffer, 0, updateSize, this.lastExtLogPosition); + fs.closeSync(fileDescriptor); + + // 将更新部分的内容转换为字符串并输出 + const updatedContent = buffer.toString('utf8'); + + // 更新上一次读取的位置 + this.lastExtLogPosition = currentSize; + const installMsg = { + type: "extInstallLogPre", + data: updatedContent + }; + + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", installMsg) + + } + + }); + return; + } + + this.lastLogPosition = 0; + this.lastExtLogPosition = 0; + const logFile = this.installConfig + '/log/install.log'; + const resultFile = this.installConfig + '/resultFile'; + const installFile = this.installConfig + '/install.sh'; + const logFile0 = this.installConfig + '/log/install.1.log'; + this.backupLogFile(logFile); + + try { + fs.writeFileSync(resultFile, ''); + fs.writeFileSync(installFile, ''); + fs.writeFileSync(logFile0, ''); + const installMsg = { + type: "depStartInstall", + depName: 'depStartInstall' + }; + + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", installMsg) + + if (this.depList.length === 0) return; + + this.depList.forEach((packageName) => { + const command = `apt-get install -y ${packageName} >> ${logFile0};\necho $? >> ${resultFile};\n`; + fs.appendFileSync(installFile, command); + }); + + this.executeScriptWithPkexec(installFile); + + } catch (error) { + console.log(error); + } + } + else if (msg.type === 'installScript') { + if (msg.script['file_name'].endsWith(".js")) { + try { + await this.loadAndInvokeFunction(this.selectObject.dirPath + '/' + msg.script['file_name'], "main"); + const scriptMsg = { + type: "scriptExecSucc", + depName: msg.script['file_name'] + }; + logger.info("脚本执行成功" + this.selectObject.dirPath + '/' + msg.script['file_name']); + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", scriptMsg) + } catch { + const scriptMsg = { + type: "scriptExecFail", + depName: msg.script['file_name'] + }; + logger.info("脚本执行失败" + this.selectObject.dirPath + '/' + msg.script['file_name']); + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", scriptMsg) + } + } else if (msg.script['file_name'].endsWith(".sh")) { + let command = "sh " + this.selectObject.dirPath + '/' + msg.script['file_name']; + // await executeCommand(command); + this.executeCommand(command).then(res => { + const scriptMsg = { + type: "scriptExecSucc", + depName: msg.script['file_name'] + }; + logger.info("脚本执行成功" + this.selectObject.dirPath + '/' + msg.script['file_name']); + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", scriptMsg) + }).catch(error => { + const scriptMsg = { + type: "scriptExecFail", + depName: msg.script['file_name'] + }; + logger.info("脚本执行失败" + this.selectObject.dirPath + '/' + msg.script['file_name']); + if (this.DeployWindow) + this.DeployWindow.webContents.send("kylinide.installWizard.msg", scriptMsg) + }); + + } + } + else if (msg.type == "cancel") { + //1.结束监听 + //2.结束正在执行的进程,下载,插件安装,软件包安装 + if (this.installProcessId && this.installProcessId.pid && this.installProcessId.exitCode == null && this.flagPkexec == 0) { + console.log("取消安装before"); + // var killcommand = `pkexec sudo pkill -TERM -P ${installProcessId.pid}`; + var killcommand = `pkexec sudo pkill -f 'bash ${this.installConfig}/install.sh'`; + const sudokill = spawn(killcommand, { shell: true, stdio: 'inherit' }); + + sudokill.on('exit', (code, signal) => { + if (code == 127) { + console.log('授权失败', code); + logger.error("取消安装授权失败"); + } else { + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.removeFileLister", { type: 'installFile' }); + this.DeployWindow.webContents.send("kylinide.installWizard.removeFileLister", { type: 'extFile' }); + this.DeployWindow.webContents.send("kylinide.installWizard.removeFileLister", { type: 'pkgFile' }); + } + this.controller.abort(); + this.installQueue.splice(0); + //结束插件下载及安装 + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.cancelinstall", { type: "cancelinstall" }); + } + } + console.log('kill Exit code:', code); + }); + console.log("取消安装after"); + } else { + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.removeFileLister", { type: 'installFile' }); + this.DeployWindow.webContents.send("kylinide.installWizard.removeFileLister", { type: 'extFile' }); + this.DeployWindow.webContents.send("kylinide.installWizard.removeFileLister", { type: 'pkgFile' }); + } + this.controller.abort(); + this.cancelFlag = true; + this.installQueue.splice(0); + //结束插件下载及安装 + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.cancelinstall", { type: "cancelinstall" }); + } + } + } + }); + // validatedIpcMain.on('kylinide.installWizard.skipWizard', event => { // logger.info("跳过操作"); // }) } //#endregion + executeScriptWithPkexec(scriptPath: string) { + this.flagPkexec = 0; + console.log("executeScriptWithPkexec"); + try { + const pkexecCommand = `pkexec sudo bash ${scriptPath}`; + this.installProcessId = + spawn(pkexecCommand, { shell: true, stdio: 'inherit' }); + + this.installProcessId.on('exit', (code, signal) => { + this.flagPkexec++; + console.log('Exit code:', code); + if (code == 127) { + //授权失败,结束安装。 + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.Auth", { type: "pkgNotAuth" }); + } + } + }); + } catch (error) { + console.error(`执行脚本时出错: ${error.message}`); + // return false; + } + } + + // 获取备份文件名 + getBackupFileName(filePath: string, index: number) { + const path = require('path'); + const extname = path.extname(filePath); + const basename = path.basename(filePath, extname); + return `${basename}.${index}${extname}`; + } + + // 备份日志文件 + backupLogFile(filePath: string) { + const path = require('path'); + for (let i = this.maxBackupFiles - 1; i >= 0; i--) { + const currentFileName = i === 0 ? filePath : this.getBackupFileName(filePath, i); + const nextFileName = this.getBackupFileName(filePath, i + 1); + const currentFilePath = path.join(path.dirname(filePath), currentFileName); + const nextFilePath = path.join(path.dirname(filePath), nextFileName); + + if (fs.existsSync(currentFilePath)) { + if (i === this.maxBackupFiles - 1) { + if (fs.existsSync(nextFileName)) + fs.unlinkSync(nextFilePath); + } + fs.copyFileSync(currentFilePath, nextFilePath); + } + } + } + + async executeNextInstall() { + if (this.installQueue.length === 0) { + return; // 队列为空,结束执行 + } + if (this.cancelFlag) { + this.installQueue.splice(0); + return; + } + const { installCommand, msg } = this.installQueue[0]; + try { + const extlogFile = this.installConfig + '/log/extInstall.1.log'; + + let ret: { stdout: string, stderr: string } = await this.spawnCommand(installCommand) as { stdout: string; stderr: string; }; + if (ret.stdout) + fs.appendFileSync(extlogFile, ret.stdout); + // if (ret.stderr) + // fs.appendFileSync(extlogFile, ret.stderr); + + const installMsg = { + type: "extInstalled", + extFileName: msg.downloadDone, + // extTotal: Object.keys(extMap).length, + extTotal: this.extList.length + }; + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.msg", installMsg); + } + } catch (error) { + console.error(`执行出错: ${error.message}`); + const extlogFile = this.installConfig + '/log/extInstall.1.log'; + fs.appendFileSync(extlogFile, installCommand); + fs.appendFileSync(extlogFile, '\n'); + fs.appendFileSync(extlogFile, error.message); + fs.appendFileSync(extlogFile, '\n'); + const installMsg = { + type: "extInstalledFail", + extFileName: msg.downloadDone, + extTotal: this.extList.length, + extName: msg.extName + }; + if (this.DeployWindow) { + this.DeployWindow.webContents.send("kylinide.installWizard.msg", installMsg); + } + } + + this.installQueue.shift(); // 执行完成后,将当前消息从队列中移除 + await this.executeNextInstall(); // 继续执行下一个消息 + } + + spawnCommand(command: string) { + return new Promise((resolve, reject) => { + const child = spawn(command, { shell: true }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + child.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + child.on('close', (code) => { + if (code === 0) { + resolve({ stdout, stderr }); + } else { + reject(new Error(`Command failed with exit code ${code} `)); + } + }); + }); + } + + async executeCommand(command: string) { + return new Promise((resolve, reject) => { + const { exec } = require('child_process'); + exec(command, (error: any, stdout: any, stderr: any) => { + if (error) { + reject(error); + } else { + // console.log(`stdout: ${ stdout } `); + // console.error(`stderr1: ${ stderr } `); + resolve(); + } + }); + }); + } + minWindow() { if (this.DeployWindow) { this.DeployWindow.minimize(); @@ -226,7 +583,7 @@ export class DeployMainService implements IDeployMainService { validatedIpcMain.on('kylinide.installWizard.init', async (event, msg) => { console.log("main on kylinide.installWizard.init"); let selectPageShow = 0; - let message: string; + let message: string = ""; if (msg.type === "httpVerify") { this.checkHttp().then(async res => { console.log("ipc main kylinide.installWizard.init httpVerify"); @@ -275,7 +632,7 @@ export class DeployMainService implements IDeployMainService { type: "error", title: "配置文件检查", buttons: ["OK"], - detail: `点击【OK】按钮后,将跳过此引导步骤,请根据开发需要自行下载插件、安装软件依赖。具体信息请查看日志${installConfig}/log/instguide.log`, + detail: `点击【OK】按钮后,将跳过此引导步骤,请根据开发需要自行下载插件、安装软件依赖。具体信息请查看日志${this.installConfig}/log/instguide.log`, message: "安装引导器不支持当前系统。" }).then(result => { if (this.DeployWindow && this.DeployWindow != undefined) @@ -289,7 +646,7 @@ export class DeployMainService implements IDeployMainService { type: "error", title: "配置文件检查", buttons: ["OK"], - detail: `点击【OK】按钮后,将跳过此引导步骤,请根据开发需要自行下载插件、安装软件依赖。具体信息请查看日志${installConfig}/log/instguide.log`, + detail: `点击【OK】按钮后,将跳过此引导步骤,请根据开发需要自行下载插件、安装软件依赖。具体信息请查看日志${this.installConfig}/log/instguide.log`, message: "本地数据及网络数据校验失败" }).then(result => { if (this.DeployWindow && this.DeployWindow != undefined) diff --git a/src/vs/platform/issue/electron-main/utils.ts b/src/vs/platform/issue/electron-main/utils.ts index 323c19b3..dbd9f7cb 100644 --- a/src/vs/platform/issue/electron-main/utils.ts +++ b/src/vs/platform/issue/electron-main/utils.ts @@ -1,5 +1,5 @@ -import * as os from 'os'; -import * as fs from 'fs'; +// import * as os from 'os'; +// import * as fs from 'fs'; //获取全局变量g_index.json下载网络 export function get_g_index_url() { @@ -9,9 +9,11 @@ export function get_g_index_url() { export function configDir() { // 获取用户目录 + const os = require('os'); + const fs = require('fs'); const userHome = os.homedir(); // 创建日志目录 - const configDir = `${userHome}/.config/Kylin-IDE/installconfig`; + const configDir = `${userHome}/.config/Kylin-Code/installconfig`; if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); // 递归创建目录 }