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
import (
@ -11,6 +11,7 @@ import (
"runtime"
"strconv"
"strings"
"time"
"github.com/b3log/wide/conf"
"github.com/b3log/wide/file"
@ -20,23 +21,24 @@ import (
"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) {
session, _ := session.HTTPSession.Get(r, "wide-session")
sid := session.Values["id"].(string)
httpSession, _ := session.HTTPSession.Get(r, "wide-session")
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"}
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{}{}
for {
if err := editorWS[sid].ReadJSON(&args); err != nil {
if err := session.EditorWS[sid].Conn.ReadJSON(&args); err != nil {
if err.Error() == "EOF" {
return
}
@ -73,14 +75,14 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
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())
return
}
}
}
// 自动完成(代码补全).
// AutocompleteHandler handles request of code autocompletion.
func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
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)
// FIXME: 使用 gocode set lib-path 在多工作空间环境下肯定是有问题的,需要考虑其他实现方式
// FIXME: using gocode set lib-path has some issues while accrossing workspaces
gocode := conf.Wide.GetExecutableInGOBIN("gocode")
argv := []string{"set", "lib-path", libPath}
exec.Command(gocode, argv...).Run()
@ -157,7 +159,7 @@ func AutocompleteHandler(w http.ResponseWriter, r *http.Request) {
w.Write(output)
}
// 查看表达式信息.
// GetExprInfoHandler handles request of getting expression infomation.
func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
@ -203,7 +205,6 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-info", "."}
cmd := exec.Command(ide_stub, argv...)
@ -229,7 +230,7 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) {
data["info"] = exprInfo
}
// 查找声明.
// FindDeclarationHandler handles request of finding declaration.
func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
@ -275,7 +276,6 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
// glog.Infof("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找声明,后续需要重新实现
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-def", "."}
cmd := exec.Command(ide_stub, argv...)
@ -309,7 +309,7 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) {
data["cursorCh"] = cursorCh
}
// 查找使用.
// FindUsagesHandler handles request of finding usages.
func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
@ -355,7 +355,6 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
offset := getCursorOffset(code, line, ch)
// glog.Infof("offset [%d]", offset)
// TODO: 目前是调用 liteide_stub 工具来查找使用,后续需要重新实现
ide_stub := conf.Wide.GetExecutableInGOBIN("ide_stub")
argv := []string{"type", "-cursor", filename + ":" + strconv.Itoa(offset), "-use", "."}
cmd := exec.Command(ide_stub, argv...)
@ -396,18 +395,19 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) {
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) {
lines := strings.Split(code, "\n")
// 计算前几行长度
// calculate sum length of lines before
for i := 0; i < line; i++ {
offset += len(lines[i])
}
// 计算当前行、当前列长度
// calculate length of the current line and column
curLine := lines[line]
var buffer bytes.Buffer
r := []rune(curLine)
@ -415,8 +415,8 @@ func getCursorOffset(code string, line, ch int) (offset int) {
buffer.WriteString(string(r[i]))
}
offset += line // 加换行符
offset += len(buffer.String()) // 加当前行列偏移
offset += len(buffer.String()) // append length of current line
offset += line // append number of '\n'
return offset
}

View File

@ -15,8 +15,9 @@ import (
"github.com/golang/glog"
)
// 格式化 Go 源码文件.
// 根据用户的 GoFormat 配置选择格式化工具:
// GoFmtHandler handles request of formatting Go source code.
//
// This function will select a format tooll based on user's configuration:
// 1. gofmt
// 2. goimports
func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
@ -38,8 +39,8 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
filePath := args["file"].(string)
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
}
@ -88,8 +89,8 @@ func GoFmtHandler(w http.ResponseWriter, r *http.Request) {
}
}
// 格式化 HTML 文件.
// FIXME:依赖的工具 gohtml 格式化 HTML 时有问题
// HTMLFmtHandler handles request of formatting HTML source code.
// FIXME: gohtml has some issues...
func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
@ -143,68 +144,3 @@ func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) {
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
import "github.com/golang/glog"
const (
EvtCodeGOPATHNotFound = iota // 事件代码:找不到环境变量 $GOPATH
EvtCodeGOROOTNotFound // 事件代码:找不到环境变量 $GOROOT
EvtCodeGocodeNotFound // 事件代码:找不到 gocode
EvtCodeIDEStubNotFound // 事件代码:找不到 IDE stub
EvtCodeServerInternalError // 事件代码:服务器内部错误
EvtCodeGOPATHNotFound = iota // event code: not found $GOPATH env variable
EvtCodeGOROOTNotFound // event code: not found $GOROOT env variable
EvtCodeGocodeNotFound // event code: not found gocode
EvtCodeIDEStubNotFound // event code: not found ide_stub
EvtCodeServerInternalError // event code: server internal error
)
// 事件队列最大长度.
// Max length of queue.
const MaxQueueLength = 10
// 事件结构.
// Event.
type Event struct {
Code int `json:"code"` // 事件代码
Sid string `json:"sid"` // 用户会话 id
Data interface{} `json:"data"` // 事件数据
Code int `json:"code"` // event code
Sid string `json:"sid"` // wide session id related
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)
// 用户事件队列.
// User event queue.
type UserEventQueue struct {
Sid string // 关联的会话 id
Queue chan *Event // 队列
Handlers []Handler // 事件处理器集
Sid string // wide session id related
Queue chan *Event // queue
Handlers []Handler // event handlers
}
// 事件队列集类型.
type Queues map[string]*UserEventQueue
// 用户事件队列集.
// User event queues.
//
// <sid, *UserEventQueue>
var UserEventQueues = Queues{}
// 加载事件处理.
// Load initializes the event handling.
func Load() {
go func() {
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 {
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) {
for _, handler := range handlers {
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 {
q := ueqs[sid]
if nil != q {
@ -80,11 +79,11 @@ func (ueqs Queues) New(sid string) *UserEventQueue {
ueqs[sid] = q
go func() { // 队列开始监听事件
go func() { // start listening
for evt := range q.Queue {
glog.V(5).Infof("Session [%s] received a event [%d]", sid, evt.Code)
// 将事件交给事件处理器进行处理
// process event by each handlers
for _, handler := range q.Handlers {
handler.Handle(evt)
}
@ -94,7 +93,7 @@ func (ueqs Queues) New(sid string) *UserEventQueue {
return q
}
// 关闭一个用户事件队列.
// Close closes a user event queue with the specified wide session id.
func (ueqs Queues) Close(sid string) {
q := ueqs[sid]
if nil == q {
@ -104,15 +103,15 @@ func (ueqs Queues) Close(sid string) {
delete(ueqs, sid)
}
// 事件处理接口.
// Type of event handler.
type Handler interface {
Handle(event *Event)
}
// 函数指针包装.
// Type of handler function.
type HandleFunc func(event *Event)
// 事件处理默认实现.
// Default implementation of event handling.
func (fn HandleFunc) Handle(event *Event) {
fn(event)
}

View File

@ -23,7 +23,7 @@ type FileNode struct {
Name string `json:"name"`
Path string `json:"path"`
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"`
FileNodes []*FileNode `json:"children"`
}

View File

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

View File

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

View File

@ -1,4 +1,4 @@
// 通知.
// Notification manipulations.
package notification
import (
@ -16,15 +16,15 @@ import (
)
const (
Error = "ERROR" // 通知.严重程度:ERROR
Warn = "WARN" // 通知.严重程度:WARN
Info = "INFO" // 通知.严重程度:INFO
Error = "ERROR" // notification.severity: ERROR
Warn = "WARN" // notification.severity: WARN
Info = "INFO" // notification.severity: INFO
Setup = "Setup" // 通知.类型:安装
Server = "Server" // 通知.类型:服务器
Setup = "Setup" // notification.type: setup
Server = "Server" // notification.type: server
)
// 通知结构.
// Notification.
type Notification struct {
event *event.Event
Type string `json:"type"`
@ -32,9 +32,9 @@ type Notification struct {
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) {
if nil == session.NotificationWS[e.Sid] {
return
@ -71,7 +71,7 @@ func event2Notification(e *event.Event) {
wsChannel.Refresh()
}
// 建立通知通道.
// WSHandler handles request of creating notification channel.
func WSHandler(w http.ResponseWriter, r *http.Request) {
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))
// 添加用户事件处理器
// add user event handler
wSession.EventQueue.AddHandler(event.HandleFunc(event2Notification))
input := map[string]interface{}{}

View File

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

View File

@ -8,18 +8,18 @@ import (
"github.com/golang/glog"
)
// 进程集类型.
// Type of process set.
type procs map[string][]*os.Process
// 所有用户正在运行的程序进程集.
// Processse of all users.
//
// <sid, []*os.Process>
var processes = procs{}
// 排它锁,防止并发修改.
// Exclusive lock.
var mutex sync.Mutex
// 添加用户执行进程.
// add adds the specified process to the user process set.
func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
mutex.Lock()
defer mutex.Unlock()
@ -30,13 +30,13 @@ func (procs *procs) add(wSession *session.WideSession, proc *os.Process) {
userProcesses = append(userProcesses, proc)
(*procs)[sid] = userProcesses
// 会话关联进程
// bind process with wide session
wSession.SetProcesses(userProcesses)
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) {
mutex.Lock()
defer mutex.Unlock()
@ -48,10 +48,10 @@ func (procs *procs) remove(wSession *session.WideSession, proc *os.Process) {
var newProcesses []*os.Process
for i, p := range userProcesses {
if p.Pid == proc.Pid {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...) // remove it
(*procs)[sid] = newProcesses
// 会话关联进程
// bind process with wide session
wSession.SetProcesses(newProcesses)
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) {
mutex.Lock()
defer mutex.Unlock()
@ -80,7 +80,7 @@ func (procs *procs) kill(wSession *session.WideSession, pid int) {
newProcesses = append(userProcesses[:i], userProcesses[i+1:]...)
(*procs)[sid] = newProcesses
// 会话关联进程
// bind process with wide session
wSession.SetProcesses(newProcesses)
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 会话:主要用于验证登录
// 2. Wide 会话:浏览器 tab 打开/刷新会创建一个,并和 HTTP 会话进行关联
// 1. HTTP session: mainly used for login authentication
// 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
import (
@ -26,52 +26,55 @@ import (
)
const (
SessionStateActive = iota // 会话状态:活的
SessionStateClosed // 会话状态:已关闭(这个状态目前暂时没有使用到)
SessionStateActive = iota // session state: active
SessionStateClosed // session state: closed (not used so far)
)
var (
// 会话通道. <sid, *util.WSChannel>
// Session channels. <sid, *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{}
// 通知通道. <sid, *util.WSChannel>
// Notification channels. <sid, *util.WSChannel>
NotificationWS = map[string]*util.WSChannel{}
)
// 用户 HTTP 会话,用于验证登录.
// HTTP session store.
var HTTPSession = sessions.NewCookieStore([]byte("BEYOND"))
// Wide 会话,对应一个浏览器 tab.
// Wide session, associated with a browser tab.
type WideSession struct {
Id string // 唯一标识
Username string // 用户名
HTTPSession *sessions.Session // 关联的 HTTP 会话
Processes []*os.Process // 关联的进程集
EventQueue *event.UserEventQueue // 关联的事件队列
State int // 状态
Content *conf.LatestSessionContent // 最近一次会话内容
Created time.Time // 创建时间
Updated time.Time // 最近一次使用时间
Id string // id
Username string // username
HTTPSession *sessions.Session // HTTP session related
Processes []*os.Process // process set
EventQueue *event.UserEventQueue // event queue
State int // state
Content *conf.LatestSessionContent // the latest session content
Created time.Time // create time
Updated time.Time // the latest use time
}
// 会话集类型.
// Type of wide sessions.
type Sessions []*WideSession
// 所有 Wide 会话集.
// Wide sessions.
var WideSessions Sessions
// 排它锁,防止并发修改.
// Exclusive lock.
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() {
go func() {
// 1 小时进行一次检查
for _ = range time.Tick(time.Hour) {
hour, _ := time.ParseDuration("-30m")
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) {
sid := r.URL.Query()["sid"][0]
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) {
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
@ -157,7 +162,7 @@ func SaveContent(w http.ResponseWriter, r *http.Request) {
for _, user := range conf.Wide.Users {
if user.Name == wSession.Username {
// 更新配置内存变量conf.FixedTimeSave() 会负责定时持久化
// update the variable in-memory, conf.FixedTimeSave() function will persist it periodically
user.LatestSessionContent = wSession.Content
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) {
s.Processes = ps
s.Refresh()
}
// 刷新会话最近一次使用时间.
// Refresh refreshes the channel by updating its use time.
func (s *WideSession) Refresh() {
s.Updated = time.Now()
}
// 创建一个 Wide 会话.
// New creates a wide session.
func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
mutex.Lock()
defer mutex.Unlock()
@ -189,7 +194,7 @@ func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
id := strconv.Itoa(rand.Int())
now := time.Now()
// 创建用户事件队列
// create user event queue
userEventQueue := event.UserEventQueues.New(id)
ret := &WideSession{
@ -208,7 +213,7 @@ func (sessions *Sessions) New(httpSession *sessions.Session) *WideSession {
return ret
}
// 获取 Wide 会话.
// Get gets a wide session with the specified session id.
func (sessions *Sessions) Get(sid string) *WideSession {
mutex.Lock()
defer mutex.Unlock()
@ -222,26 +227,26 @@ func (sessions *Sessions) Get(sid string) *WideSession {
return nil
}
// 移除 Wide 会话,释放相关资源.
// Remove removes a wide session specified with the given session id, releases resources associated with it.
//
// 会话相关资源:
// Session-related resources:
//
// 1. 用户事件队列
// 2. 运行中的进程集
// 3. WebSocket 通道
// 1. user event queue
// 2. process set
// 3. websocket channels
func (sessions *Sessions) Remove(sid string) {
mutex.Lock()
defer mutex.Unlock()
for i, s := range *sessions {
if s.Id == sid {
// 从会话集中移除
// remove from session set
*sessions = append((*sessions)[:i], (*sessions)[i+1:]...)
// 关闭用户事件队列
// close user event queue
event.UserEventQueues.Close(sid)
// 杀进程
// kill processes
for _, p := range s.Processes {
if err := p.Kill(); nil != err {
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 {
ws.Close()
delete(OutputWS, sid)
@ -268,12 +273,13 @@ func (sessions *Sessions) Remove(sid string) {
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 {
if s.HTTPSession.ID == s.HTTPSession.ID {
cnt++
}
}
glog.V(3).Infof("User [%s] has [%d] sessions", s.Username, cnt)
return
@ -281,7 +287,7 @@ func (sessions *Sessions) Remove(sid string) {
}
}
// 获取 username 指定的用户的所有 Wide 会话.
// GetByUsername gets wide sessions.
func (sessions *Sessions) GetByUsername(username string) []*WideSession {
mutex.Lock()
defer mutex.Unlock()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import (
"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{}) {
w.Header().Set("Content-Type", "application/json")

View File

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