This commit is contained in:
Liang Ding 2014-09-15 18:03:52 +08:00
parent db0c3992d0
commit 24c75e8125
6 changed files with 127 additions and 66 deletions

View File

@ -9,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time"
"github.com/b3log/wide/event" "github.com/b3log/wide/event"
_ "github.com/b3log/wide/i18n" _ "github.com/b3log/wide/i18n"
@ -38,35 +39,42 @@ type conf struct {
var Wide conf var Wide conf
var rawWide conf var rawWide conf
// 检查 Wide 运行环境. // 定时检查 Wide 运行环境.
// 如果是特别严重的问题(比如 $GOPATH 不存在)则退出进程。另一些不太严重的问题(比如 gocode 不存在)则放入全局通知队列。 // 如果是特别严重的问题(比如 $GOPATH 不存在)则退出进程。另一些不太严重的问题(比如 gocode 不存在)则放入全局通知队列。
func (*conf) CheckEnv() { func CheckEnv() {
if "" == os.Getenv("GOPATH") { go func() {
glog.Fatal("Not found $GOPATH") for {
os.Exit(-1)
}
if "" == os.Getenv("GOROOT") { if "" == os.Getenv("GOPATH") {
glog.Fatal("Not found $GOROOT") glog.Fatal("Not found $GOPATH")
os.Exit(-1)
}
os.Exit(-1) if "" == os.Getenv("GOROOT") {
} glog.Fatal("Not found $GOROOT")
gocode := Wide.GetGocode() os.Exit(-1)
cmd := exec.Command(gocode, "close") }
_, err := cmd.Output()
if nil != err {
event.EventQueue <- event.EvtGocodeNotFount
glog.Warningf("Not found gocode [%s]", gocode)
}
ide_stub := Wide.GetIDEStub() gocode := Wide.GetGocode()
cmd = exec.Command(ide_stub, "version") cmd := exec.Command(gocode, "close")
_, err = cmd.Output() _, err := cmd.Output()
if nil != err { if nil != err {
event.EventQueue <- event.EvtIDEStubNotFound event.EventQueue <- event.EvtGocodeNotFount
glog.Warningf("Not found ide_stub [%s]", ide_stub) glog.Warningf("Not found gocode [%s]", gocode)
} }
ide_stub := Wide.GetIDEStub()
cmd = exec.Command(ide_stub, "version")
_, err = cmd.Output()
if nil != err {
event.EventQueue <- event.EvtIDEStubNotFound
glog.Warningf("Not found ide_stub [%s]", ide_stub)
}
time.Sleep(time.Second * 2)
}
}()
} }
// 获取 username 指定的用户的工作空间路径. // 获取 username 指定的用户的工作空间路径.
@ -140,9 +148,6 @@ func Save() bool {
// 加载 Wide 配置. // 加载 Wide 配置.
func Load() { func Load() {
// 检查 Wide 运行环境
Wide.CheckEnv()
bytes, _ := ioutil.ReadFile("conf/wide.json") bytes, _ := ioutil.ReadFile("conf/wide.json")
err := json.Unmarshal(bytes, &Wide) err := json.Unmarshal(bytes, &Wide)

View File

@ -1,38 +1,55 @@
// 事件处理. // 事件处理.
package event package event
import ( import "github.com/golang/glog"
"github.com/golang/glog"
)
const ( const (
EvtGOPATHNotFound = iota // 事件:找不到环境变量 $GOPATH EvtGOPATHNotFound = iota // 事件:找不到环境变量 $GOPATH
EvtGOROOTNotFound // 事件:找不到环境变量 $GOROOT EvtGOROOTNotFound // 事件:找不到环境变量 $GOROOT
EvtGocodeNotFount // 事件:找不到 gocode EvtGocodeNotFount // 事件:找不到 gocode
EvtIDEStubNotFound // 事件:找不到 IDE stub EvtIDEStubNotFound // 事件:找不到 IDE stub
) )
const MaxQueueLength = 10
// 全局事件队列. // 全局事件队列.
// 入队的事件将分发到每个用户的通知队列. // 入队的事件将分发到每个用户的通知队列.
var EventQueue = make(chan int, 10) var EventQueue = make(chan int, MaxQueueLength)
// 用户事件队列. // 用户事件队列.
// 入队的事件将翻译为通知,并通过通知通道推送到前端. // 入队的事件将翻译为通知,并通过通知通道推送到前端.
var UserEventQueue map[string]chan int var UserEventQueues = map[string]chan int{}
// 加载事件处理. // 加载事件处理.
func Load() { func Load() {
go func() { go func() {
for { for event := range EventQueue {
// 获取事件
event := <-EventQueue
glog.V(5).Info("收到全局事件 [%d]", event) glog.V(5).Info("收到全局事件 [%d]", event)
// 将事件分发到每个用户的事件队列里 // 将事件分发到每个用户的事件队列里
for _, userQueue := range UserEventQueue { for _, userQueue := range UserEventQueues {
userQueue <- event userQueue <- event
} }
} }
}() }()
} }
// 添加一个用户事件队列.
func InitUserQueue(sid string) {
// FIXME: 会话过期后需要销毁对应的用户事件队列
q := UserEventQueues[sid]
if nil != q {
close(q)
}
q = make(chan int, MaxQueueLength)
UserEventQueues[sid] = q
go func() {
for event := range q {
glog.Infof("Session [%s] received a event [%d]", sid, event)
}
}()
}

View File

@ -13,6 +13,7 @@ import (
"github.com/b3log/wide/event" "github.com/b3log/wide/event"
"github.com/b3log/wide/file" "github.com/b3log/wide/file"
"github.com/b3log/wide/i18n" "github.com/b3log/wide/i18n"
"github.com/b3log/wide/notification"
"github.com/b3log/wide/output" "github.com/b3log/wide/output"
"github.com/b3log/wide/shell" "github.com/b3log/wide/shell"
"github.com/b3log/wide/user" "github.com/b3log/wide/user"
@ -31,6 +32,9 @@ func init() {
// 加载配置 // 加载配置
conf.Load() conf.Load()
// 定时检查 Wide 运行环境
conf.CheckEnv()
} }
// Wide 首页. // Wide 首页.
@ -108,6 +112,9 @@ func main() {
http.HandleFunc("/shell/ws", shell.WSHandler) http.HandleFunc("/shell/ws", shell.WSHandler)
http.HandleFunc("/shell", shell.IndexHandler) http.HandleFunc("/shell", shell.IndexHandler)
// 通知
http.HandleFunc("/notification/ws", notification.WSHandler)
// 用户 // 用户
http.HandleFunc("/user/new", user.AddUser) http.HandleFunc("/user/new", user.AddUser)
http.HandleFunc("/user/repos/init", user.InitGitRepos) http.HandleFunc("/user/repos/init", user.InitGitRepos)

View File

@ -6,9 +6,10 @@ import (
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strings" "time"
"github.com/b3log/wide/conf" "github.com/b3log/wide/conf"
"github.com/b3log/wide/event"
"github.com/b3log/wide/user" "github.com/b3log/wide/user"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
@ -22,25 +23,42 @@ type Notification struct {
Message string Message string
} }
// 一个用户会话的 WebSocket 通道结构.
type WSChannel struct {
Conn *websocket.Conn // WebSocket 连接
Time time.Time // 该通道最近一次使用时间
}
// 通知通道. // 通知通道.
var notificationWS = map[string]*websocket.Conn{} // <username, {<sid1, WSChannel1>, <sid2, WSChannel2>}>
var notificationWSs = map[string]map[string]WSChannel{}
func WSHandler(w http.ResponseWriter, r *http.Request) { func WSHandler(w http.ResponseWriter, r *http.Request) {
session, _ := user.Session.Get(r, "wide-session") session, _ := user.Session.Get(r, "wide-session")
username := session.Values["username"].(string) username := session.Values["username"].(string)
sid := session.Values["id"].(string) sid := session.Values["id"].(string)
notificationWS[sid], _ = websocket.Upgrade(w, r, nil, 1024, 1024) conn, _ := websocket.Upgrade(w, r, nil, 1024, 1024)
wsChan := WSChannel{Conn: conn, Time: time.Now()}
wsChans := notificationWSs[username]
if nil == wsChans {
wsChans = map[string]WSChannel{}
}
wsChans[sid] = wsChan
ret := map[string]interface{}{"output": "Notification initialized", "cmd": "init-notification"} ret := map[string]interface{}{"output": "Notification initialized", "cmd": "init-notification"}
notificationWS[sid].WriteJSON(&ret) wsChan.Conn.WriteJSON(&ret)
glog.Infof("Open a new [Notification] with session [%s], %d", sid, len(notificationWS)) glog.Infof("Open a new [Notification] with session [%s], %d", sid, len(wsChans))
event.InitUserQueue(sid)
input := map[string]interface{}{} input := map[string]interface{}{}
for { for {
if err := notificationWS[sid].ReadJSON(&input); err != nil { if err := wsChan.Conn.ReadJSON(&input); err != nil {
if err.Error() == "EOF" { if err.Error() == "EOF" {
return return
} }
@ -49,35 +67,16 @@ func WSHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
glog.Error("Shell WS ERROR: " + err.Error()) glog.Error("Notification WS ERROR: " + err.Error())
return return
} }
inputCmd := input["cmd"].(string)
cmds := strings.Split(inputCmd, "|")
commands := []*exec.Cmd{}
for _, cmdWithArgs := range cmds {
cmdWithArgs = strings.TrimSpace(cmdWithArgs)
cmdWithArgs := strings.Split(cmdWithArgs, " ")
args := []string{}
if len(cmdWithArgs) > 1 {
args = cmdWithArgs[1:]
}
cmd := exec.Command(cmdWithArgs[0], args...)
commands = append(commands, cmd)
}
output := "" output := ""
if !strings.Contains(inputCmd, "clear") {
output = pipeCommands(username, commands...)
}
ret = map[string]interface{}{"output": output, "cmd": "shell-output"} ret = map[string]interface{}{"output": output, "cmd": "notification-output"}
if err := notificationWS[sid].WriteJSON(&ret); err != nil { if err := wsChan.Conn.WriteJSON(&ret); err != nil {
glog.Error("Shell WS ERROR: " + err.Error()) glog.Error("Notification WS ERROR: " + err.Error())
return return
} }
} }

28
static/js/notification.js Normal file
View File

@ -0,0 +1,28 @@
var notificationWS = new WebSocket(config.channel.shell + '/notification/ws');
notificationWS.onopen = function() {
console.log('[notification onopen] connected');
};
notificationWS.onmessage = function(e) {
console.log('[notification onmessage]' + e.data);
var data = JSON.parse(e.data);
if ('init-notification' !== data.cmd) {
$('#notification').val(data.output);
}
};
notificationWS.onclose = function(e) {
console.log('[notification onclose] disconnected (' + e.code + ')');
delete notificationWS;
};
notificationWS.onerror = function(e) {
console.log('[notification onerror] ' + e);
};
var notification = {
init: function() {
}
};
$(document).ready(function() {
notification.init();
});

View File

@ -143,6 +143,10 @@
<textarea id="output"></textarea> <textarea id="output"></textarea>
</div> </div>
</div> </div>
<textarea id="notification"></textarea>
</div> </div>
</div> </div>
@ -196,6 +200,7 @@
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/lint/go-lint.js?{{.Wide.StaticResourceVersion}}"></script> <script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/lint/go-lint.js?{{.Wide.StaticResourceVersion}}"></script>
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/tabs.js?{{.Wide.StaticResourceVersion}}"></script> <script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/tabs.js?{{.Wide.StaticResourceVersion}}"></script>
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/editor.js?{{.Wide.StaticResourceVersion}}"></script> <script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/editor.js?{{.Wide.StaticResourceVersion}}"></script>
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/notification.js?{{.Wide.StaticResourceVersion}}"></script>
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/tree.js?{{.Wide.StaticResourceVersion}}"></script> <script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/tree.js?{{.Wide.StaticResourceVersion}}"></script>
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/wide.js?{{.Wide.StaticResourceVersion}}"></script> <script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/wide.js?{{.Wide.StaticResourceVersion}}"></script>
<script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/menu.js?{{.Wide.StaticResourceVersion}}"></script> <script type="text/javascript" src="{{.Wide.StaticServer}}/static/js/menu.js?{{.Wide.StaticResourceVersion}}"></script>