From c516a7e2893732ffe7fc68d68dd4398850c1cce9 Mon Sep 17 00:00:00 2001 From: Liang Ding Date: Tue, 16 Sep 2014 17:32:55 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=90=E8=A1=8C=E7=9A=84=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=BB=B4=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 2 +- notification/notifications.go | 2 +- output/outputs.go | 47 +++++++++++++--- output/processes.go | 55 ++++++++++++++++++ shell/shells.go | 2 +- static/js/wide.js | 101 ++++++++++++++++++---------------- 6 files changed, 150 insertions(+), 59 deletions(-) create mode 100644 output/processes.go diff --git a/main.go b/main.go index aa1a5fa..0fcee6d 100644 --- a/main.go +++ b/main.go @@ -24,7 +24,7 @@ import ( func init() { // 默认启动参数 flag.Set("logtostderr", "true") - flag.Set("v", "1") + flag.Set("v", "3") flag.Parse() // 加载事件处理 diff --git a/notification/notifications.go b/notification/notifications.go index fb74bd0..4eb6629 100644 --- a/notification/notifications.go +++ b/notification/notifications.go @@ -32,7 +32,7 @@ type Notification struct { } // 通知通道. -// +// var notificationWSs = map[string]*util.WSChannel{} // 用户事件处理:将事件转为通知,并通过通知通道推送给前端. diff --git a/output/outputs.go b/output/outputs.go index 1ff1c69..779a69c 100644 --- a/output/outputs.go +++ b/output/outputs.go @@ -21,7 +21,7 @@ import ( ) // 输出通道. -// +// var outputWS = map[string]*util.WSChannel{} // 建立输出通道. @@ -83,9 +83,18 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { reader := io.MultiReader(stdout, stderr) - cmd.Start() + if err := cmd.Start(); nil != err { + glog.Error(err) + data["succ"] = false + + return + } + + // 添加到用户进程集中 + processes.add(sid, cmd.Process) channelRet := map[string]interface{}{} + channelRet["pid"] = cmd.Process.Pid go func(runningId int) { glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath) @@ -95,16 +104,33 @@ func RunHandler(w http.ResponseWriter, r *http.Request) { count, err := reader.Read(buf) if nil != err || 0 == count { - glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath) + // 从用户进程集中移除这个执行完毕的进程 + processes.remove(sid, cmd.Process) - break - } else { - channelRet["output"] = string(buf[:count]) - channelRet["cmd"] = "run" + glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath) if nil != outputWS[sid] { wsChannel := outputWS[sid] + channelRet["cmd"] = "run-done" + channelRet["output"] = string(buf[:count]) + err := wsChannel.Conn.WriteJSON(&channelRet) + if nil != err { + glog.Error(err) + break + } + + // 更新通道最近使用时间 + wsChannel.Time = time.Now() + } + + break + } else { + if nil != outputWS[sid] { + wsChannel := outputWS[sid] + + channelRet["cmd"] = "run" + channelRet["output"] = string(buf[:count]) err := wsChannel.Conn.WriteJSON(&channelRet) if nil != err { glog.Error(err) @@ -206,7 +232,12 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) { if data["succ"].(bool) { reader := io.MultiReader(stdout, stderr) - cmd.Start() + if err := cmd.Start(); nil != err { + glog.Error(err) + data["succ"] = false + + return + } go func(runningId int) { glog.V(3).Infof("Session [%s] is building [id=%d, file=%s]", sid, runningId, filePath) diff --git a/output/processes.go b/output/processes.go new file mode 100644 index 0000000..e6429e8 --- /dev/null +++ b/output/processes.go @@ -0,0 +1,55 @@ +package output + +import ( + "os" + + "github.com/golang/glog" +) + +// 所有用户正在运行的程序进程集. +// +type procs map[string][]*os.Process + +var processes = procs{} + +// 添加用户执行进程. +func (procs *procs) add(sid string, proc *os.Process) { + userProcesses := (*procs)[sid] + + userProcesses = append(userProcesses, proc) + (*procs)[sid] = userProcesses + + glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid])) +} + +// 移除用户执行进程. +func (procs *procs) remove(sid string, proc *os.Process) { + userProcesses := (*procs)[sid] + + var newProcesses []*os.Process + for i, p := range userProcesses { + if p.Pid == proc.Pid { + newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) + (*procs)[sid] = newProcesses + + glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid])) + + return + } + } +} + +// 结束用户正在执行的进程. +func (procs *procs) kill(sid string, pid int) { + pros := (*procs)[sid] + + for _, p := range pros { + if p.Pid == pid { + if err := p.Kill(); nil != err { + glog.Error("Kill a process [pid=%d] of session [%s] failed [error=%v]", pid, sid, err) + } else { + glog.V(3).Infof("Killed a process [pid=%d] of session [%s]", pid, sid) + } + } + } +} diff --git a/shell/shells.go b/shell/shells.go index f7199bd..d81a56d 100644 --- a/shell/shells.go +++ b/shell/shells.go @@ -21,7 +21,7 @@ import ( ) // Shell 通道. -// > +// > var shellWS = map[string]*util.WSChannel{} // Shell 首页. diff --git a/static/js/wide.js b/static/js/wide.js index 2359e83..59834c1 100644 --- a/static/js/wide.js +++ b/static/js/wide.js @@ -1,9 +1,9 @@ var outputWS = new WebSocket(config.channel.output + '/output/ws'); -outputWS.onopen = function() { +outputWS.onopen = function () { console.log('[output onopen] connected'); }; -outputWS.onmessage = function(e) { +outputWS.onmessage = function (e) { console.log('[output onmessage]' + e.data); var data = JSON.parse(e.data); @@ -11,8 +11,29 @@ outputWS.onmessage = function(e) { goLintFound = []; } - if ('run' === data.cmd) { + if ('run' === data.nextCmd) { + var request = { + executable: data.executable + }; + + $.ajax({ + type: 'POST', + url: '/run', + data: JSON.stringify(request), + dataType: "json", + beforeSend: function (data) { + $('#output').text(''); + }, + success: function (data) { + + } + }); + } + + if ('run' === data.cmd) { // 正在运行 $('#output').text($('#output').text() + data.output); + } else if ('run-done' === data.cmd) { // 运行结束 + // TODO: 运行结束后修改 [构建&运行] 图标状态为可用状态 } else if ('build' === data.cmd || 'go install' === data.cmd) { $('#output').text(data.output); @@ -24,6 +45,8 @@ outputWS.onmessage = function(e) { to: CodeMirror.Pos(lint.lineNo, 0), message: lint.msg, severity: lint.severity}); } + + // TODO: 修改 [构建&运行] 图标状态为可用状态 } // 触发一次 gutter lint @@ -31,49 +54,28 @@ outputWS.onmessage = function(e) { } else if ('go get' === data.cmd || 'go install' === data.cmd) { $('#output').text($('#output').text() + data.output); } - - if ('build' === data.cmd) { - if ('run' === data.nextCmd) { - var request = { - executable: data.executable - }; - - $.ajax({ - type: 'POST', - url: '/run', - data: JSON.stringify(request), - dataType: "json", - beforeSend: function(data) { - $('#output').text(''); - }, - success: function(data) { - - } - }); - } - } }; -outputWS.onclose = function(e) { +outputWS.onclose = function (e) { console.log('[output onclose] disconnected (' + e.code + ')'); delete outputWS; }; -outputWS.onerror = function(e) { +outputWS.onerror = function (e) { console.log('[output onerror] ' + e); }; var wide = { curNode: undefined, curEditor: undefined, - _initLayout: function() { + _initLayout: function () { var mainH = $(window).height() - $(".menu").height() - $(".footer").height() - 2; $(".content, .ztree").height(mainH); $(".edit-panel").height(mainH - $(".output").height()); }, - init: function() { + init: function () { this._initLayout(); - $("body").bind("mousedown", function(event) { + $("body").bind("mousedown", function (event) { if (!(event.target.id === "dirRMenu" || $(event.target).closest("#dirRMenu").length > 0)) { $("#dirRMenu").hide(); } @@ -90,7 +92,7 @@ var wide = { }); }, - saveFile: function() { + saveFile: function () { var request = { file: $(".edit-header .current span:eq(0)").attr("title"), code: wide.curEditor.getValue() @@ -100,41 +102,44 @@ var wide = { url: '/file/save', data: JSON.stringify(request), dataType: "json", - success: function(data) { + success: function (data) { } }); }, - saveAllFiles: function() { + saveAllFiles: function () { // TODO: save all files }, - closeFile: function() { + closeFile: function () { // TODO: close file }, - closeAllFiles: function() { + closeAllFiles: function () { // TODO: close all files }, - exit: function() { + exit: function () { // TODO: exit }, - run: function() { + // 构建 & 运行. + run: function () { var request = { file: $(".edit-header .current span:eq(0)").attr("title"), code: wide.curEditor.getValue() }; + // TODO: 修改 [构建&运行] 图标状态为不可用状态 + $.ajax({ type: 'POST', url: '/build', data: JSON.stringify(request), dataType: "json", - beforeSend: function(data) { + beforeSend: function (data) { $('#output').text(''); }, - success: function(data) { + success: function (data) { } }); }, - goget: function() { + goget: function () { var request = { file: $(".edit-header .current span:eq(0)").attr("title") }; @@ -144,14 +149,14 @@ var wide = { url: '/go/get', data: JSON.stringify(request), dataType: "json", - beforeSend: function(data) { + beforeSend: function (data) { $('#output').text(''); }, - success: function(data) { + success: function (data) { } }); }, - goinstall: function() { + goinstall: function () { var request = { file: $(".edit-header .current span:eq(0)").attr("title"), code: wide.curEditor.getValue() @@ -162,14 +167,14 @@ var wide = { url: '/go/install', data: JSON.stringify(request), dataType: "json", - beforeSend: function(data) { + beforeSend: function (data) { $('#output').text(''); }, - success: function(data) { + success: function (data) { } }); }, - fmt: function() { + fmt: function () { var path = $(".edit-header .current span:eq(0)").attr("title"); var mode = wide.curNode.mode; @@ -187,7 +192,7 @@ var wide = { url: '/go/fmt', data: JSON.stringify(request), dataType: "json", - success: function(data) { + success: function (data) { if (data.succ) { wide.curEditor.setValue(data.code); } @@ -201,7 +206,7 @@ var wide = { url: '/html/fmt', data: JSON.stringify(request), dataType: "json", - success: function(data) { + success: function (data) { if (data.succ) { wide.curEditor.setValue(data.code); } @@ -228,7 +233,7 @@ var wide = { } }; -$(document).ready(function() { +$(document).ready(function () { wide.init(); tree.init(); menu.init();