This commit is contained in:
Liang Ding 2014-10-29 18:15:18 +08:00
parent 7ec8a77005
commit cfd5367919
17 changed files with 210 additions and 281 deletions

View File

@ -1,4 +1,4 @@
// 编辑器操作. // Editor manipulations.
package editor package editor
import ( import (
@ -11,6 +11,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/file" "github.com/b3log/wide/file"
@ -20,23 +21,24 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
var editorWS = map[string]*websocket.Conn{} // WSHandler handles request of creating editor channel.
// 建立编辑器通道.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
session, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
sid := session.Values["id"].(string) sid := httpSession.Values["id"].(string)
editorWS[sid], _ = websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
editorChan := util.WSChannel{Sid: sid, Conn: conn, Request: r, Time: time.Now()}
session.EditorWS[sid] = &editorChan
ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"} ret := map[string]interface{}{"output": "Editor initialized", "cmd": "init-editor"}
editorWS[sid].WriteJSON(&ret) editorChan.Conn.WriteJSON(&ret)
glog.Infof("Open a new [Editor] with session [%s], %d", sid, len(editorWS)) glog.Infof("Open a new [Editor] with session [%s], %d", sid, len(session.EditorWS))
args := map[string]interface{}{} args := map[string]interface{}{}
for { for {
if err := editorWS[sid].ReadJSON(&args); err != nil { if err := session.EditorWS[sid].Conn.ReadJSON(&args); err != nil {
if err.Error() == "EOF" { if err.Error() == "EOF" {
return return
} }
@ -73,14 +75,14 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"} ret = map[string]interface{}{"output": string(output.Bytes()), "cmd": "autocomplete"}
if err := editorWS[sid].WriteJSON(&ret); err != nil { if err := session.EditorWS[sid].Conn.WriteJSON(&ret); err != nil {
glog.Error("Editor WS ERROR: " + err.Error()) glog.Error("Editor WS ERROR: " + err.Error())
return return
} }
} }
} }
// 自动完成(代码补全). // AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) { func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
var args map[string]interface{} var args map[string]interface{}
@ -133,7 +135,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
glog.V(5).Infof("gocode set lib-path %s", libPath) glog.V(5).Infof("gocode set lib-path %s", libPath)
// FIXME: 使用 gocode set lib-path 在多工作空间环境下肯定是有问题的,需要考虑其他实现方式 // FIXME: using gocode set lib-path has some issues while accrossing workspaces
gocode := conf.Wide.GetExecutableInGOBIN("gocode") gocode := conf.Wide.GetExecutableInGOBIN("gocode")
argv := []string{"set", "lib-path", libPath} argv := []string{"set", "lib-path", libPath}
exec.Command(gocode, argv...).Run() exec.Command(gocode, argv...).Run()
@ -157,7 +159,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
w.Write(output) w.Write(output)
} }
// 查看表达式信息. // GetExprInfoHandler handles request of getting expression infomation.
func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) { func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -203,7 +205,6 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset [%d]", offset) // glog.Infof("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub") ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."} argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."}
cmd := exec.Command(ide_stub, argv...) cmd := exec.Command(ide_stub, argv...)
@ -229,7 +230,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
data["info"] = exprInfo data["info"] = exprInfo
} }
// 查找声明. // FindDeclarationHandler handles request of finding declaration.
func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) { func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -275,7 +276,6 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset [%d]", offset) // glog.Infof("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub") ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."} argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."}
cmd := exec.Command(ide_stub, argv...) cmd := exec.Command(ide_stub, argv...)
@ -309,7 +309,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
data["cursorCh"] = cursorCh data["cursorCh"] = cursorCh
} }
// 查找使用. // FindUsagesHandler handles request of finding usages.
func FindUsagesHandler(w http.ResponseWriter, r *http.Request) { func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -355,7 +355,6 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch) offset := getCursorOffset(code, line, ch)
// glog.Infof("offset [%d]", offset) // glog.Infof("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找使用,后续需要重新实现
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub") ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."} argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."}
cmd := exec.Command(ide_stub, argv...) cmd := exec.Command(ide_stub, argv...)
@ -396,18 +395,19 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
data["founds"] = usages data["founds"] = usages
} }
// 计算光标偏移位置. // getCursorOffset calculates the cursor offset.
// //
// line 指定了行号(第一行为 0ch 指定了列号(第一列为 0. // line is the line number, starts with 0 that means the first line
// ch is the column number, starts with 0 that means the first column
func getCursorOffset(code string, line, ch int) (offset int) { func getCursorOffset(code string, line, ch int) (offset int) {
lines := strings.Split(code, "\n") lines := strings.Split(code, "\n")
// 计算前几行长度 // calculate sum length of lines before
for i := 0; i < line; i++ { for i := 0; i < line; i++ {
offset += len(lines[i]) offset += len(lines[i])
} }
// 计算当前行、当前列长度 // calculate length of the current line and column
curLine := lines[line] curLine := lines[line]
var buffer bytes.Buffer var buffer bytes.Buffer
r := []rune(curLine) r := []rune(curLine)
@ -415,8 +415,8 @@ func getCursorOffset(code string, line, ch int) (offset int) {
buffer.WriteString(string(r[i])) buffer.WriteString(string(r[i]))
} }
offset += line // 加换行符 offset += len(buffer.String()) // append length of current line
offset += len(buffer.String()) // 加当前行列偏移 offset += line // append number of '\n'
return offset return offset
} }

View File

@ -15,8 +15,9 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// 格式化 Go 源码文件. // GoFmtHandler handles request of formatting Go source code.
// 根据用户的 GoFormat 配置选择格式化工具: //
// This function will select a format tooll based on user's configuration:
// 1. gofmt // 1. gofmt
// 2. goimports // 2. goimports
func GoFmtHandler(w http.ResponseWriter, r *http.Request) { func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
@ -38,8 +39,8 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string) filePath := args["file"].(string)
apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg" apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg"
if strings.HasPrefix(filePath, apiPath) { // 如果是 Go API 源码文件 if strings.HasPrefix(filePath, apiPath) { // if it is Go API source code
// 忽略修改 // ignore it
return return
} }
@ -88,8 +89,8 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// 格式化 HTML 文件. // HTMLFmtHandler handles request of formatting HTML source code.
// FIXME:依赖的工具 gohtml 格式化 HTML 时有问题 // FIXME: gohtml has some issues...
func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) { func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -143,68 +144,3 @@ func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
} }
// 格式化 JSON 文件.
func JSONFmtHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
var args map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
glog.Error(err)
data["succ"] = false
return
}
filePath := args["file"].(string)
fout, err := os.Create(filePath)
if nil != err {
glog.Error(err)
data["succ"] = false
return
}
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
obj := new(interface{})
if err := json.Unmarshal([]byte(code), &obj); nil != err {
glog.Error(err)
data["succ"] = false
return
}
glog.Info(obj)
bytes, err := json.MarshalIndent(obj, "", " ")
if nil != err {
data["succ"] = false
return
}
code = string(bytes)
data["code"] = code
fout, err = os.Create(filePath)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
data["succ"] = false
return
}
}

View File

@ -1,53 +1,52 @@
// 事件处理. // Event manipulations.
package event package event
import "github.com/golang/glog" import "github.com/golang/glog"
const ( const (
EvtCodeGOPATHNotFound = iota // 事件代码:找不到环境变量 $GOPATH EvtCodeGOPATHNotFound = iota // event code: not found $GOPATH env variable
EvtCodeGOROOTNotFound // 事件代码:找不到环境变量 $GOROOT EvtCodeGOROOTNotFound // event code: not found $GOROOT env variable
EvtCodeGocodeNotFound // 事件代码:找不到 gocode EvtCodeGocodeNotFound // event code: not found gocode
EvtCodeIDEStubNotFound // 事件代码:找不到 IDE stub EvtCodeIDEStubNotFound // event code: not found ide_stub
EvtCodeServerInternalError // 事件代码:服务器内部错误 EvtCodeServerInternalError // event code: server internal error
) )
// 事件队列最大长度. // Max length of queue.
const MaxQueueLength = 10 const MaxQueueLength = 10
// 事件结构. // Event.
type Event struct { type Event struct {
Code int `json:"code"` // 事件代码 Code int `json:"code"` // event code
Sid string `json:"sid"` // 用户会话 id Sid string `json:"sid"` // wide session id related
Data interface{} `json:"data"` // 事件数据 Data interface{} `json:"data"` // event data
} }
// 全局事件队列. // Global event queue.
// //
// 入队的事件将分发到每个用户的事件队列中. // Every event in this queue will be dispatched to each user event queue.
var EventQueue = make(chan *Event, MaxQueueLength) var EventQueue = make(chan *Event, MaxQueueLength)
// 用户事件队列. // User event queue.
type UserEventQueue struct { type UserEventQueue struct {
Sid string // 关联的会话 id Sid string // wide session id related
Queue chan *Event // 队列 Queue chan *Event // queue
Handlers []Handler // 事件处理器集 Handlers []Handler // event handlers
} }
// 事件队列集类型.
type Queues map[string]*UserEventQueue type Queues map[string]*UserEventQueue
// 用户事件队列集. // User event queues.
// //
// <sid, *UserEventQueue> // <sid, *UserEventQueue>
var UserEventQueues = Queues{} var UserEventQueues = Queues{}
// 加载事件处理. // Load initializes the event handling.
func Load() { func Load() {
go func() { go func() {
for event := range EventQueue { for event := range EventQueue {
glog.V(5).Infof("收到全局事件 [%d]", event.Code) glog.V(5).Infof("Received a global event [code=%d]", event.Code)
// 将事件分发到每个用户的事件队列里 // dispatch the event to each user event queue
for _, userQueue := range UserEventQueues { for _, userQueue := range UserEventQueues {
event.Sid = userQueue.Sid event.Sid = userQueue.Sid
@ -57,14 +56,14 @@ func Load() {
}() }()
} }
// 为用户队列添加事件处理器. // AddHandler adds the specified handlers to user event queues.
func (uq *UserEventQueue) AddHandler(handlers ...Handler) { func (uq *UserEventQueue) AddHandler(handlers ...Handler) {
for _, handler := range handlers { for _, handler := range handlers {
uq.Handlers = append(uq.Handlers, handler) uq.Handlers = append(uq.Handlers, handler)
} }
} }
// 初始化一个用户事件队列. // New initializes a user event queue with the specified wide session id.
func (ueqs Queues) New(sid string) *UserEventQueue { func (ueqs Queues) New(sid string) *UserEventQueue {
q := ueqs[sid] q := ueqs[sid]
if nil != q { if nil != q {
@ -80,11 +79,11 @@ func (ueqs Queues) New(sid string) *UserEventQueue {
ueqs[sid] = q ueqs[sid] = q
go func() { // 队列开始监听事件 go func() { // start listening
for evt := range q.Queue { for evt := range q.Queue {
glog.V(5).Infof("Session [%s] received a event [%d]", sid, evt.Code) glog.V(5).Infof("Session [%s] received a event [%d]", sid, evt.Code)
// 将事件交给事件处理器进行处理 // process event by each handlers
for _, handler := range q.Handlers { for _, handler := range q.Handlers {
handler.Handle(evt) handler.Handle(evt)
} }
@ -94,7 +93,7 @@ func (ueqs Queues) New(sid string) *UserEventQueue {
return q return q
} }
// 关闭一个用户事件队列. // Close closes a user event queue with the specified wide session id.
func (ueqs Queues) Close(sid string) { func (ueqs Queues) Close(sid string) {
q := ueqs[sid] q := ueqs[sid]
if nil == q { if nil == q {
@ -104,15 +103,15 @@ func (ueqs Queues) Close(sid string) {
delete(ueqs, sid) delete(ueqs, sid)
} }
// 事件处理接口. // Type of event handler.
type Handler interface { type Handler interface {
Handle(event *Event) Handle(event *Event)
} }
// 函数指针包装. // Type of handler function.
type HandleFunc func(event *Event) type HandleFunc func(event *Event)
// 事件处理默认实现. // Default implementation of event handling.
func (fn HandleFunc) Handle(event *Event) { func (fn HandleFunc) Handle(event *Event) {
fn(event) fn(event)
} }

View File

@ -23,7 +23,7 @@ type FileNode struct {
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path"` Path string `json:"path"`
IconSkin string `json:"iconSkin"` // Value should be end with a space IconSkin string `json:"iconSkin"` // Value should be end with a space
Type string `json:"type"` // "f": file"d": directory Type string `json:"type"` // "f": file, "d": directory
Mode string `json:"mode"` Mode string `json:"mode"`
FileNodes []*FileNode `json:"children"` FileNodes []*FileNode `json:"children"`
} }

View File

@ -1,4 +1,4 @@
// 国际化操作. // Internationalization manipulations.
package i18n package i18n
import ( import (
@ -10,16 +10,17 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// Locale.
type locale struct { type locale struct {
Name string Name string
Langs map[string]interface{} Langs map[string]interface{}
TimeZone string TimeZone string
} }
// 所有的 locales. // All locales.
var Locales = map[string]locale{} var Locales = map[string]locale{}
// 加载国际化配置. // Load loads i18n message configurations.
func Load() { func Load() {
f, _ := os.Open("i18n") f, _ := os.Open("i18n")
names, _ := f.Readdirnames(-1) names, _ := f.Readdirnames(-1)
@ -57,12 +58,12 @@ func load(localeStr string) {
glog.V(5).Infof("Loaded [%s] locale configuration", localeStr) glog.V(5).Infof("Loaded [%s] locale configuration", localeStr)
} }
// 获取语言配置项. // Get gets message with the specified locale and key.
func Get(locale, key string) interface{} { func Get(locale, key string) interface{} {
return Locales[locale].Langs[key] return Locales[locale].Langs[key]
} }
// 获取语言配置. // GetAll gets all messages with the specified locale.
func GetAll(locale string) map[string]interface{} { func GetAll(locale string) map[string]interface{} {
return Locales[locale].Langs return Locales[locale].Langs
} }

View File

@ -309,7 +309,6 @@ func main() {
http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler)) http.HandleFunc("/find/decl", handlerWrapper(editor.FindDeclarationHandler))
http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler)) http.HandleFunc("/find/usages", handlerWrapper(editor.FindUsagesHandler))
http.HandleFunc("/html/fmt", handlerWrapper(editor.HTMLFmtHandler)) http.HandleFunc("/html/fmt", handlerWrapper(editor.HTMLFmtHandler))
http.HandleFunc("/json/fmt", handlerWrapper(editor.JSONFmtHandler))
// shell // shell
http.HandleFunc("/shell/ws", handlerWrapper(shell.WSHandler)) http.HandleFunc("/shell/ws", handlerWrapper(shell.WSHandler))

View File

@ -1,4 +1,4 @@
// 通知. // Notification manipulations.
package notification package notification
import ( import (
@ -16,15 +16,15 @@ import (
) )
const ( const (
Error = "ERROR" // 通知.严重程度:ERROR Error = "ERROR" // notification.severity: ERROR
Warn = "WARN" // 通知.严重程度:WARN Warn = "WARN" // notification.severity: WARN
Info = "INFO" // 通知.严重程度:INFO Info = "INFO" // notification.severity: INFO
Setup = "Setup" // 通知.类型:安装 Setup = "Setup" // notification.type: setup
Server = "Server" // 通知.类型:服务器 Server = "Server" // notification.type: server
) )
// 通知结构. // Notification.
type Notification struct { type Notification struct {
event *event.Event event *event.Event
Type string `json:"type"` Type string `json:"type"`
@ -32,9 +32,9 @@ type Notification struct {
Message string `json:"message"` Message string `json:"message"`
} }
// 用户事件处理:将事件转为通知,并通过通知通道推送给前端. // event2Notification processes user event by converting the specified event to a notification, and then push it to front
// browser with notification channel.
// //
// 当用户事件队列接收到事件时将会调用该函数进行处理.
func event2Notification(e *event.Event) { func event2Notification(e *event.Event) {
if nil == session.NotificationWS[e.Sid] { if nil == session.NotificationWS[e.Sid] {
return return
@ -71,7 +71,7 @@ func event2Notification(e *event.Event) {
wsChannel.Refresh() wsChannel.Refresh()
} }
// 建立通知通道. // WSHandler handles request of creating notification channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
@ -89,7 +89,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
glog.V(4).Infof("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS)) glog.V(4).Infof("Open a new [Notification] with session [%s], %d", sid, len(session.NotificationWS))
// 添加用户事件处理器 // add user event handler
wSession.EventQueue.AddHandler(event.HandleFunc(event2Notification)) wSession.EventQueue.AddHandler(event.HandleFunc(event2Notification))
input := map[string]interface{}{} input := map[string]interface{}{}

View File

@ -1,4 +1,4 @@
// 构建、运行、go tool 操作. // Build, run and go tool manipulations.
package output package output
import ( import (
@ -25,11 +25,11 @@ import (
) )
const ( const (
lintSeverityError = "error" // Lint 严重级别:错误 lintSeverityError = "error" // lint severity: error
lintSeverityWarn = "warning" // Lint 严重级别:警告 lintSeverityWarn = "warning" // lint severity: warning
) )
// 代码 Lint 结构. // Code lint.
type Lint struct { type Lint struct {
File string `json:"file"` File string `json:"file"`
LineNo int `json:"lineNo"` LineNo int `json:"lineNo"`
@ -37,7 +37,7 @@ type Lint struct {
Msg string `json:"msg"` Msg string `json:"msg"`
} }
// 建立输出通道. // WSHandler handles request of creating output channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
@ -52,7 +52,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS)) glog.V(4).Infof("Open a new [Output] with session [%s], %d", sid, len(session.OutputWS))
} }
// 运行一个可执行文件. // RunHandler handles request of executing a binary file.
func RunHandler(w http.ResponseWriter, r *http.Request) { func RunHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -105,7 +105,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 添加到用户进程集中 // add the process to user's process set
processes.add(wSession, cmd.Process) processes.add(wSession, cmd.Process)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
@ -117,7 +117,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath) glog.V(3).Infof("Session [%s] is running [id=%d, file=%s]", sid, runningId, filePath)
// 在读取程序输出前先返回一次,使前端获取到 run 状态与 pid // push once for front-end to get the 'run' state and pid
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
wsChannel := session.OutputWS[sid] wsChannel := session.OutputWS[sid]
@ -129,15 +129,14 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
for { for {
buf, err := reader.ReadBytes('\n') buf, err := reader.ReadBytes('\n')
if nil != err || 0 == len(buf) { if nil != err || 0 == len(buf) {
// 从用户进程集中移除这个执行完毕(或是被主动停止)的进程 // remove the exited process from user process set
processes.remove(wSession, cmd.Process) processes.remove(wSession, cmd.Process)
glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath) glog.V(3).Infof("Session [%s] 's running [id=%d, file=%s] has done", sid, runningId, filePath)
@ -153,8 +152,7 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
break break
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
break break
@ -170,15 +168,14 @@ func RunHandler(w http.ResponseWriter, r *http.Request) {
break break
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
} }
} }
}(rand.Int()) }(rand.Int())
} }
// 构建可执行文件. // BuildHandler handles request of building.
func BuildHandler(w http.ResponseWriter, r *http.Request) { func BuildHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -237,7 +234,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
executable = filepath.Join(curDir, executable) executable = filepath.Join(curDir, executable)
// 先把可执行文件删了 // remove executable file before building
err = os.RemoveAll(executable) err = os.RemoveAll(executable)
if nil != err { if nil != err {
glog.Info(err) glog.Info(err)
@ -269,7 +266,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始构建” // display "START [go build]" in front-end browser
channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n" channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n"
channelRet["cmd"] = "start-build" channelRet["cmd"] = "start-build"
@ -282,8 +279,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) reader := bufio.NewReader(io.MultiReader(stdout, stderr))
@ -301,19 +297,18 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
glog.V(3).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir) glog.V(3).Infof("Session [%s] is building [id=%d, dir=%s]", sid, runningId, curDir)
// 一次性读取 // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "build" channelRet["cmd"] = "build"
channelRet["executable"] = executable channelRet["executable"] = executable
if 0 == len(buf) { // 说明构建成功,没有错误信息输出 if 0 == len(buf) { // build success
// 设置下一次执行命令(前端会根据该参数发送请求)
channelRet["nextCmd"] = args["nextCmd"] channelRet["nextCmd"] = args["nextCmd"]
channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n" channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n"
go func() { // 运行 go install生成的库用于 gocode lib-path go func() { // go install, for subsequent gocode lib-path
cmd := exec.Command("go", "install") cmd := exec.Command("go", "install")
cmd.Dir = curDir cmd.Dir = curDir
@ -324,15 +319,16 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
glog.Warning(string(out)) glog.Warning(string(out))
} }
}() }()
} else { // 构建失败 } else { // build error
// 解析错误信息,返回给编辑器 gutter lint // build gutter lint
errOut := string(buf) errOut := string(buf)
channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" + errOut channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" + errOut
lines := strings.Split(errOut, "\n") lines := strings.Split(errOut, "\n")
if lines[0][0] == '#' { if lines[0][0] == '#' {
lines = lines[1:] // 跳过第一行 lines = lines[1:] // skip the first line
} }
lints := []*Lint{} lints := []*Lint{}
@ -343,7 +339,7 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
} }
if line[0] == '\t' { if line[0] == '\t' {
// 添加到上一个 lint 中 // append to the last lint
last := len(lints) last := len(lints)
msg := lints[last-1].Msg msg := lints[last-1].Msg
msg += line msg += line
@ -385,14 +381,13 @@ func BuildHandler(w http.ResponseWriter, r *http.Request) {
glog.Error(err) glog.Error(err)
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
}(rand.Int()) }(rand.Int())
} }
// go test. // GoTestHandler handles request of go test.
func GoTestHandler(w http.ResponseWriter, r *http.Request) { func GoTestHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -443,7 +438,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始 go test // display "START [go test]" in front-end browser
channelRet["output"] = "<span class='start-test'>" + i18n.Get(locale, "start-test").(string) + "</span>\n" channelRet["output"] = "<span class='start-test'>" + i18n.Get(locale, "start-test").(string) + "</span>\n"
channelRet["cmd"] = "start-test" channelRet["cmd"] = "start-test"
@ -456,8 +451,7 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) reader := bufio.NewReader(io.MultiReader(stdout, stderr))
@ -477,10 +471,10 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go test" channelRet["cmd"] = "go test"
// 一次性读取 // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
// 同步点,等待 go test 执行完成 // waiting for go test finished
cmd.Wait() cmd.Wait()
if !cmd.ProcessState.Success() { if !cmd.ProcessState.Success() {
@ -501,13 +495,12 @@ func GoTestHandler(w http.ResponseWriter, r *http.Request) {
glog.Error(err) glog.Error(err)
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
}(rand.Int()) }(rand.Int())
} }
// go install. // GoInstallHandler handles request of go install.
func GoInstallHandler(w http.ResponseWriter, r *http.Request) { func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -560,7 +553,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始 go install” // display "START [go install]" in front-end browser
channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n" channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n"
channelRet["cmd"] = "start-install" channelRet["cmd"] = "start-install"
@ -573,8 +566,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) reader := bufio.NewReader(io.MultiReader(stdout, stderr))
@ -592,19 +584,20 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
glog.V(3).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir) glog.V(3).Infof("Session [%s] is running [go install] [id=%d, dir=%s]", sid, runningId, curDir)
// 一次性读取 // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go install" channelRet["cmd"] = "go install"
if 0 != len(buf) { // 构建失败 if 0 != len(buf) { // build error
// 解析错误信息,返回给编辑器 gutter lint // build gutter lint
errOut := string(buf) errOut := string(buf)
lines := strings.Split(errOut, "\n") lines := strings.Split(errOut, "\n")
if lines[0][0] == '#' { if lines[0][0] == '#' {
lines = lines[1:] // 跳过第一行 lines = lines[1:] // skip the first line
} }
lints := []*Lint{} lints := []*Lint{}
@ -615,7 +608,7 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
} }
if line[0] == '\t' { if line[0] == '\t' {
// 添加到上一个 lint 中 // append to the last lint
last := len(lints) last := len(lints)
msg := lints[last-1].Msg msg := lints[last-1].Msg
msg += line msg += line
@ -661,14 +654,13 @@ func GoInstallHandler(w http.ResponseWriter, r *http.Request) {
glog.Error(err) glog.Error(err)
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
}(rand.Int()) }(rand.Int())
} }
// go get. // GoGetHandler handles request of go get.
func GoGetHandler(w http.ResponseWriter, r *http.Request) { func GoGetHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -719,7 +711,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
if nil != session.OutputWS[sid] { if nil != session.OutputWS[sid] {
// 在前端 output 中显示“开始 go get // display "START [go get]" in front-end browser
channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n" channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-get").(string) + "</span>\n"
channelRet["cmd"] = "start-get" channelRet["cmd"] = "start-get"
@ -732,8 +724,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
reader := bufio.NewReader(io.MultiReader(stdout, stderr)) reader := bufio.NewReader(io.MultiReader(stdout, stderr))
@ -754,7 +745,7 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
channelRet := map[string]interface{}{} channelRet := map[string]interface{}{}
channelRet["cmd"] = "go get" channelRet["cmd"] = "go get"
// 一次性读取 // read all
buf, _ := ioutil.ReadAll(reader) buf, _ := ioutil.ReadAll(reader)
if 0 != len(buf) { if 0 != len(buf) {
@ -776,13 +767,12 @@ func GoGetHandler(w http.ResponseWriter, r *http.Request) {
glog.Error(err) glog.Error(err)
} }
// 更新通道最近使用时间 wsChannel.Refresh()
wsChannel.Time = time.Now()
} }
}(rand.Int()) }(rand.Int())
} }
// 结束正在运行的进程. // StopHandler handles request of stoping a running process.
func StopHandler(w http.ResponseWriter, r *http.Request) { func StopHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)

View File

@ -8,18 +8,18 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// 进程集类型. // Type of process set.
type procs map[string][]*os.Process type procs map[string][]*os.Process
// 所有用户正在运行的程序进程集. // Processse of all users.
// //
// <sid, []*os.Process> // <sid, []*os.Process>
var processes = procs{} var processes = procs{}
// 排它锁,防止并发修改. // Exclusive lock.
var mutex sync.Mutex var mutex sync.Mutex
// 添加用户执行进程. // add adds the specified process to the user process set.
func (procs *procs) add(wSession *session.WideSession, proc *os.Process) { func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@ -30,13 +30,13 @@ func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
userProcesses = append(userProcesses, proc) userProcesses = append(userProcesses, proc)
(*procs)[sid] = userProcesses (*procs)[sid] = userProcesses
// 会话关联进程 // bind process with wide session
wSession.SetProcesses(userProcesses) wSession.SetProcesses(userProcesses)
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid])) glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
} }
// 移除用户执行进程. // remove removes the specified process from the user process set.
func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) { func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@ -48,10 +48,10 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
var newProcesses []*os.Process var newProcesses []*os.Process
for i, p := range userProcesses { for i, p := range userProcesses {
if p.Pid == proc.Pid { if p.Pid == proc.Pid {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
(*procs)[sid] = newProcesses (*procs)[sid] = newProcesses
// 会话关联进程 // bind process with wide session
wSession.SetProcesses(newProcesses) wSession.SetProcesses(newProcesses)
glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid])) glog.V(3).Infof("Session [%s] has [%d] processes", sid, len((*procs)[sid]))
@ -61,7 +61,7 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
} }
} }
// 结束用户正在执行的进程. // kill kills a process specified by the given pid.
func (procs *procs) kill(wSession *session.WideSession, pid int) { func (procs *procs) kill(wSession *session.WideSession, pid int) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@ -80,7 +80,7 @@ func (procs *procs) kill(wSession *session.WideSession, pid int) {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses (*procs)[sid] = newProcesses
// 会话关联进程 // bind process with wide session
wSession.SetProcesses(newProcesses) wSession.SetProcesses(newProcesses)
glog.V(3).Infof("Killed a process [pid=%d] of session [%s]", pid, sid) glog.V(3).Infof("Killed a process [pid=%d] of session [%s]", pid, sid)

View File

@ -1,11 +1,11 @@
// 会话操作. // Session manipulations.
// //
// Wide 服务器端需要维护两种会话: // Wide server side needs maintain two kinds of sessions:
// //
// 1. HTTP 会话:主要用于验证登录 // 1. HTTP session: mainly used for login authentication
// 2. Wide 会话:浏览器 tab 打开/刷新会创建一个,并和 HTTP 会话进行关联 // 2. Wide session: browser tab open/refresh will create one, and associates with HTTP session
// //
// 当会话失效时:释放所有和该会话相关的资源,例如运行中的程序进程、事件队列等. // When a session gone: release all resources associated with it, such as running processes, event queues.
package session package session
import ( import (
@ -26,52 +26,55 @@ import (
) )
const ( const (
SessionStateActive = iota // 会话状态:活的 SessionStateActive = iota // session state: active
SessionStateClosed // 会话状态:已关闭(这个状态目前暂时没有使用到) SessionStateClosed // session state: closed (not used so far)
) )
var ( var (
// 会话通道. <sid, *util.WSChannel> // Session channels. <sid, *util.WSChannel>
SessionWS = map[string]*util.WSChannel{} SessionWS = map[string]*util.WSChannel{}
// 输出通道. <sid, *util.WSChannel> // Editor channels. <sid, *util.WSChannel>
EditorWS = map[string]*util.WSChannel{}
// Output channels. <sid, *util.WSChannel>
OutputWS = map[string]*util.WSChannel{} OutputWS = map[string]*util.WSChannel{}
// 通知通道. <sid, *util.WSChannel> // Notification channels. <sid, *util.WSChannel>
NotificationWS = map[string]*util.WSChannel{} NotificationWS = map[string]*util.WSChannel{}
) )
// 用户 HTTP 会话,用于验证登录. // HTTP session store.
var HTTPSession = sessions.NewCookieStore([]byte("BEYOND")) var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
// Wide 会话,对应一个浏览器 tab. // Wide session, associated with a browser tab.
type WideSession struct { type WideSession struct {
Id string // 唯一标识 Id string // id
Username string // 用户名 Username string // username
HTTPSession *sessions.Session // 关联的 HTTP 会话 HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // 关联的进程集 Processes []*os.Process // process set
EventQueue *event.UserEventQueue // 关联的事件队列 EventQueue *event.UserEventQueue // event queue
State int // 状态 State int // state
Content *conf.LatestSessionContent // 最近一次会话内容 Content *conf.LatestSessionContent // the latest session content
Created time.Time // 创建时间 Created time.Time // create time
Updated time.Time // 最近一次使用时间 Updated time.Time // the latest use time
} }
// 会话集类型. // Type of wide sessions.
type Sessions []*WideSession type Sessions []*WideSession
// 所有 Wide 会话集. // Wide sessions.
var WideSessions Sessions var WideSessions Sessions
// 排它锁,防止并发修改. // Exclusive lock.
var mutex sync.Mutex var mutex sync.Mutex
// 在一些特殊情况(例如浏览器不间断刷新/在源代码视图刷新)下 Wide 会话集内会出现无效会话该函数定时1 小时)检查并移除这些无效会话. // In some special cases (such as a browser uninterrupted refresh / refresh in the source code view) will occur
// some invalid sessions, the function checks and removes these invalid sessions periodically (1 hour).
// //
// 无效会话:在检查时间内 30 分钟都没有使用过的会话,参考 WideSession.Updated 字段. // Invalid sessions: sessions that not used within 30 minutes, refers to WideSession.Updated field.
func FixedTimeRelease() { func FixedTimeRelease() {
go func() { go func() {
// 1 小时进行一次检查
for _ = range time.Tick(time.Hour) { for _ = range time.Tick(time.Hour) {
hour, _ := time.ParseDuration("-30m") hour, _ := time.ParseDuration("-30m")
threshold := time.Now().Add(hour) threshold := time.Now().Add(hour)
@ -87,7 +90,9 @@ func FixedTimeRelease() {
}() }()
} }
// 建立会话通道. 通道断开时销毁会话状态,回收相关资源. // WSHandler handles request of creating session channel.
//
// When a channel closed, releases all resources associated with it.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
sid := r.URL.Query()["sid"][0] sid := r.URL.Query()["sid"][0]
wSession := WideSessions.Get(sid) wSession := WideSessions.Get(sid)
@ -129,7 +134,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
// 会话内容保存. // SaveContent handles request of session content storing.
func SaveContent(w http.ResponseWriter, r *http.Request) { func SaveContent(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true} data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data) defer util.RetJSON(w, r, data)
@ -157,7 +162,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
for _, user := range conf.Wide.Users { for _, user := range conf.Wide.Users {
if user.Name == wSession.Username { if user.Name == wSession.Username {
// 更新配置内存变量conf.FixedTimeSave() 会负责定时持久化 // update the variable in-memory, conf.FixedTimeSave() function will persist it periodically
user.LatestSessionContent = wSession.Content user.LatestSessionContent = wSession.Content
wSession.Refresh() wSession.Refresh()
@ -167,19 +172,19 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
} }
} }
// 设置会话关联的进程集. // SetProcesses binds process set with the wide session.
func (s *WideSession) SetProcesses(ps []*os.Process) { func (s *WideSession) SetProcesses(ps []*os.Process) {
s.Processes = ps s.Processes = ps
s.Refresh() s.Refresh()
} }
// 刷新会话最近一次使用时间. // Refresh refreshes the channel by updating its use time.
func (s *WideSession) Refresh() { func (s *WideSession) Refresh() {
s.Updated = time.Now() s.Updated = time.Now()
} }
// 创建一个 Wide 会话. // New creates a wide session.
func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession { func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@ -189,7 +194,7 @@ func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
id := strconv.Itoa(rand.Int()) id := strconv.Itoa(rand.Int())
now := time.Now() now := time.Now()
// 创建用户事件队列 // create user event queue
userEventQueue := event.UserEventQueues.New(id) userEventQueue := event.UserEventQueues.New(id)
ret := &WideSession{ ret := &WideSession{
@ -208,7 +213,7 @@ func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
return ret return ret
} }
// 获取 Wide 会话. // Get gets a wide session with the specified session id.
func (sessions *Sessions) Get(sid string) *WideSession { func (sessions *Sessions) Get(sid string) *WideSession {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
@ -222,26 +227,26 @@ func (sessions *Sessions) Get(sid string) *WideSession {
return nil return nil
} }
// 移除 Wide 会话,释放相关资源. // Remove removes a wide session specified with the given session id, releases resources associated with it.
// //
// 会话相关资源: // Session-related resources:
// //
// 1. 用户事件队列 // 1. user event queue
// 2. 运行中的进程集 // 2. process set
// 3. WebSocket 通道 // 3. websocket channels
func (sessions *Sessions) Remove(sid string) { func (sessions *Sessions) Remove(sid string) {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
for i, s := range *sessions { for i, s := range *sessions {
if s.Id == sid { if s.Id == sid {
// 从会话集中移除 // remove from session set
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...) *sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
// 关闭用户事件队列 // close user event queue
event.UserEventQueues.Close(sid) event.UserEventQueues.Close(sid)
// 杀进程 // kill processes
for _, p := range s.Processes { for _, p := range s.Processes {
if err := p.Kill(); nil != err { if err := p.Kill(); nil != err {
glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid) glog.Errorf("Can't kill process [%d] of session [%s]", p.Pid, sid)
@ -250,7 +255,7 @@ func (sessions *Sessions) Remove(sid string) {
} }
} }
// 回收所有通道 // close websocket channels
if ws, ok := OutputWS[sid]; ok { if ws, ok := OutputWS[sid]; ok {
ws.Close() ws.Close()
delete(OutputWS, sid) delete(OutputWS, sid)
@ -268,12 +273,13 @@ func (sessions *Sessions) Remove(sid string) {
glog.V(3).Infof("Removed a session [%s]", s.Id) glog.V(3).Infof("Removed a session [%s]", s.Id)
cnt := 0 // 统计当前 HTTP 会话关联的 Wide 会话数量 cnt := 0 // count wide sessions associated with HTTP session
for _, s := range *sessions { for _, s := range *sessions {
if s.HTTPSession.ID == s.HTTPSession.ID { if s.HTTPSession.ID == s.HTTPSession.ID {
cnt++ cnt++
} }
} }
glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt) glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt)
return return
@ -281,7 +287,7 @@ func (sessions *Sessions) Remove(sid string) {
} }
} }
// 获取 username 指定的用户的所有 Wide 会话. // GetByUsername gets wide sessions.
func (sessions *Sessions) GetByUsername(username string) []*WideSession { func (sessions *Sessions) GetByUsername(username string) []*WideSession {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()

View File

@ -1,6 +1,6 @@
package session package session
// TODO: 这个文件内的功能目前没有使用,只是开了个头 :p // TODO: this file not used currently, just a beginning :p
import ( import (
"encoding/json" "encoding/json"

View File

@ -18,12 +18,12 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
// Shell 通道. // Shell channel.
// //
// <sid, *util.WSChannel>> // <sid, *util.WSChannel>>
var ShellWS = map[string]*util.WSChannel{} var ShellWS = map[string]*util.WSChannel{}
// Shell 首页. // IndexHandler handles request of Shell index.
func IndexHandler(w http.ResponseWriter, r *http.Request) { func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
@ -36,7 +36,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
httpSession.Save(r, w) httpSession.Save(r, w)
// 创建一个 Wide 会话 // create a wide session
wideSession := session.WideSessions.New(httpSession) wideSession := session.WideSessions.New(httpSession)
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
@ -61,7 +61,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
t.Execute(w, model) t.Execute(w, model)
} }
// 建立 Shell 通道. // WSHandler handles request of creating Shell channel.
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
httpSession, _ := session.HTTPSession.Get(r, "wide-session") httpSession, _ := session.HTTPSession.Get(r, "wide-session")
username := httpSession.Values["username"].(string) username := httpSession.Values["username"].(string)
@ -122,12 +122,10 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// 更新通道最近使用时间 wsChan.Refresh()
wsChan.Time = time.Now()
} }
} }
// 以管道方式执行多个命令.
func pipeCommands(username string, commands ...*exec.Cmd) string { func pipeCommands(username string, commands ...*exec.Cmd) string {
for i, command := range commands[:len(commands)-1] { for i, command := range commands[:len(commands)-1] {
setCmdEnv(command, username) setCmdEnv(command, username)
@ -147,7 +145,7 @@ func pipeCommands(username string, commands ...*exec.Cmd) string {
out, err := last.CombinedOutput() out, err := last.CombinedOutput()
// 结束进程,释放资源 // release resources
for _, command := range commands[:len(commands)-1] { for _, command := range commands[:len(commands)-1] {
command.Wait() command.Wait()
} }

View File

@ -1,4 +1,4 @@
// 工具. // Utilities.
package util package util
import ( import (
@ -8,10 +8,10 @@ import (
type mynet struct{} type mynet struct{}
// 网络工具. // Network utilities.
var Net = mynet{} var Net = mynet{}
// 获取第一块网卡的 IP 地址. // LocalIP gets the first NIC's IP address.
func (*mynet) LocalIP() (string, error) { func (*mynet) LocalIP() (string, error) {
tt, err := net.Interfaces() tt, err := net.Interfaces()

View File

@ -9,15 +9,15 @@ import (
type myos struct{} type myos struct{}
// 操作系统工具. // OS utilities.
var OS = myos{} var OS = myos{}
// 判断是否是 Windows 操作系统. // IsWindows determines whether current OS is Windows.
func (*myos) IsWindows() bool { func (*myos) IsWindows() bool {
return "windows" == runtime.GOOS return "windows" == runtime.GOOS
} }
// 获取当前执行程序的工作目录的绝对路径. // Pwd gets the path of current working directory.
func (*myos) Pwd() string { func (*myos) Pwd() string {
file, _ := exec.LookPath(os.Args[0]) file, _ := exec.LookPath(os.Args[0])
pwd, _ := filepath.Abs(file) pwd, _ := filepath.Abs(file)

View File

@ -6,7 +6,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// panic 恢复. // Recover recovers a panic.
func Recover() { func Recover() {
if re := recover(); re != nil { if re := recover(); re != nil {
glog.Errorf("PANIC RECOVERED:\n %v, %s", re, debug.Stack()) glog.Errorf("PANIC RECOVERED:\n %v, %s", re, debug.Stack())

View File

@ -7,7 +7,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
) )
// HTTP 返回 JSON 统一处理. // RetJSON writes HTTP response with "Content-Type, application/json".
func RetJSON(w http.ResponseWriter, r *http.Request, res map[string]interface{}) { func RetJSON(w http.ResponseWriter, r *http.Request, res map[string]interface{}) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")

View File

@ -7,20 +7,20 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
// 一个用户会话的 WebSocket 通道结构. // WebSocket channel.
type WSChannel struct { type WSChannel struct {
Sid string // 用户会话 id Sid string // wide session id
Conn *websocket.Conn // WebSocket 连接 Conn *websocket.Conn // websocket connection
Request *http.Request // 关联的 HTTP 请求 Request *http.Request // HTTP request related
Time time.Time // 该通道最近一次使用时间 Time time.Time // the latest use time
} }
// 关闭通道. // Close closed the channel.
func (c *WSChannel) Close() { func (c *WSChannel) Close() {
c.Conn.Close() c.Conn.Close()
} }
// Refresh refreshes the channel by updating its Time. // Refresh refreshes the channel by updating its use time.
func (c *WSChannel) Refresh() { func (c *WSChannel) Refresh() {
c.Time = time.Now() c.Time = time.Now()
} }