diff --git a/conf/wide.go b/conf/wide.go index 20550f5..131e27a 100644 --- a/conf/wide.go +++ b/conf/wide.go @@ -75,11 +75,13 @@ func FixedTimeCheckEnv() { for _ = range time.Tick(time.Minute * 7) { if "" == os.Getenv("GOPATH") { glog.Fatal("Not found $GOPATH") + os.Exit(-1) } if "" == os.Getenv("GOROOT") { glog.Fatal("Not found $GOROOT") + os.Exit(-1) } @@ -87,7 +89,8 @@ func FixedTimeCheckEnv() { cmd := exec.Command(gocode, "close") _, err := cmd.Output() if nil != err { - event.EventQueue <- event.EvtCodeGocodeNotFound + event.EventQueue <- &event.Event{Code: event.EvtCodeGocodeNotFound} + glog.Warningf("Not found gocode [%s]", gocode) } @@ -95,7 +98,8 @@ func FixedTimeCheckEnv() { cmd = exec.Command(ide_stub, "version") _, err = cmd.Output() if nil != err { - event.EventQueue <- event.EvtCodeIDEStubNotFound + event.EventQueue <- &event.Event{Code: event.EvtCodeIDEStubNotFound} + glog.Warningf("Not found ide_stub [%s]", ide_stub) } } diff --git a/editor/editors.go b/editor/editors.go index f5a21b9..ee8cd47 100644 --- a/editor/editors.go +++ b/editor/editors.go @@ -82,11 +82,9 @@ func WSHandler(w http.ResponseWriter, r *http.Request) { // 自动完成(代码补全). func AutocompleteHandler(w http.ResponseWriter, r *http.Request) { - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) http.Error(w, err.Error(), 500) @@ -167,10 +165,8 @@ func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) { session, _ := session.HTTPSession.Get(r, "wide-session") username := session.Values["username"].(string) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) http.Error(w, err.Error(), 500) @@ -241,10 +237,8 @@ func FindDeclarationHandler(w http.ResponseWriter, r *http.Request) { session, _ := session.HTTPSession.Get(r, "wide-session") username := session.Values["username"].(string) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) http.Error(w, err.Error(), 500) @@ -323,11 +317,9 @@ func FindUsagesHandler(w http.ResponseWriter, r *http.Request) { session, _ := session.HTTPSession.Get(r, "wide-session") username := session.Values["username"].(string) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) http.Error(w, err.Error(), 500) diff --git a/editor/formatter.go b/editor/formatter.go index cf4be1c..74cb070 100644 --- a/editor/formatter.go +++ b/editor/formatter.go @@ -94,11 +94,9 @@ func HTMLFmtHandler(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false @@ -151,11 +149,9 @@ func JSONFmtHandler(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false diff --git a/event/events.go b/event/events.go index 56064de..e5fe7d0 100644 --- a/event/events.go +++ b/event/events.go @@ -4,10 +4,11 @@ package event import "github.com/golang/glog" const ( - EvtCodeGOPATHNotFound = iota // 事件代码:找不到环境变量 $GOPATH - EvtCodeGOROOTNotFound // 事件代码:找不到环境变量 $GOROOT - EvtCodeGocodeNotFound // 事件代码:找不到 gocode - EvtCodeIDEStubNotFound // 事件代码:找不到 IDE stub + EvtCodeGOPATHNotFound = iota // 事件代码:找不到环境变量 $GOPATH + EvtCodeGOROOTNotFound // 事件代码:找不到环境变量 $GOROOT + EvtCodeGocodeNotFound // 事件代码:找不到 gocode + EvtCodeIDEStubNotFound // 事件代码:找不到 IDE stub + EvtCodeServerInternalError // 事件代码:服务器内部错误 ) // 事件队列最大长度. @@ -15,20 +16,21 @@ const MaxQueueLength = 10 // 事件结构. type Event struct { - Code int `json:"code"` // 事件代码 - Sid string `json:"sid"` // 用户会话 id + Code int `json:"code"` // 事件代码 + Sid string `json:"sid"` // 用户会话 id + Data interface{} `json:"data"` // 事件数据 } // 全局事件队列. // // 入队的事件将分发到每个用户的事件队列中. -var EventQueue = make(chan int, MaxQueueLength) +var EventQueue = make(chan *Event, MaxQueueLength) // 用户事件队列. type UserEventQueue struct { - Sid string // 关联的会话 id - Queue chan int // 队列 - Handlers []Handler // 事件处理器集 + Sid string // 关联的会话 id + Queue chan *Event // 队列 + Handlers []Handler // 事件处理器集 } // 事件队列集类型. @@ -43,10 +45,12 @@ var UserEventQueues = Queues{} func Load() { go func() { for event := range EventQueue { - glog.V(5).Info("收到全局事件 [%d]", event) + glog.V(5).Infof("收到全局事件 [%d]", event.Code) // 将事件分发到每个用户的事件队列里 for _, userQueue := range UserEventQueues { + event.Sid = userQueue.Sid + userQueue.Queue <- event } } @@ -71,19 +75,18 @@ func (ueqs Queues) New(sid string) *UserEventQueue { q = &UserEventQueue{ Sid: sid, - Queue: make(chan int, MaxQueueLength), + Queue: make(chan *Event, MaxQueueLength), } ueqs[sid] = q go func() { // 队列开始监听事件 - for evtCode := range q.Queue { - glog.V(5).Infof("Session [%s] received a event [%d]", sid, evtCode) + for evt := range q.Queue { + glog.V(5).Infof("Session [%s] received a event [%d]", sid, evt.Code) // 将事件交给事件处理器进行处理 for _, handler := range q.Handlers { - handler.Handle(&Event{Code: evtCode, Sid: sid}) - + handler.Handle(evt) } } }() diff --git a/file/files.go b/file/files.go index 14b679a..13efade 100644 --- a/file/files.go +++ b/file/files.go @@ -1,4 +1,4 @@ -// 文件树操作. +// File tree manipulations. package file import ( @@ -12,32 +12,34 @@ import ( "strings" "github.com/b3log/wide/conf" + "github.com/b3log/wide/event" "github.com/b3log/wide/session" "github.com/b3log/wide/util" "github.com/golang/glog" ) -// 文件节点,用于构造文件树. +// File node, used to construct the file tree. type FileNode struct { Name string `json:"name"` Path string `json:"path"` - IconSkin string `json:"iconSkin"` // 值的末尾应该有一个空格 - Type string `json:"type"` // "f":文件,"d":文件夹 + IconSkin string `json:"iconSkin"` // Value should be end with a space + Type string `json:"type"` // "f": file,"d": directory Mode string `json:"mode"` FileNodes []*FileNode `json:"children"` } -// 代码片段. 这个结构可用于“查找使用”、“文件搜索”等的返回值. +// Source code snippet, used to as the result of "Find Usages", "Search". type Snippet struct { - Path string `json:"path"` // 文件路径 - Line int `json:"line"` // 行号 - Ch int `json:"ch"` // 列号 - Contents []string `json:"contents"` // 附近几行 + Path string `json:"path"` // file path + Line int `json:"line"` // line number + Ch int `json:"ch"` // column number + Contents []string `json:"contents"` // lines nearby } -// 构造用户工作空间文件树. +// GetFiles handles request of constructing user workspace file tree. // -// 将 Go API 源码包($GOROOT/src/pkg)也作为子节点,这样能方便用户查看 Go API 源码. +// The Go API source code package ($GOROOT/src/pkg) also as a child node, +// so that users can easily view the Go API source code. func GetFiles(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) @@ -50,7 +52,7 @@ func GetFiles(w http.ResponseWriter, r *http.Request) { root := FileNode{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", FileNodes: []*FileNode{}} - // 工作空间节点处理 + // workspace node process for _, workspace := range workspaces { workspacePath := workspace + conf.PathSeparator + "src" @@ -60,45 +62,43 @@ func GetFiles(w http.ResponseWriter, r *http.Request) { walk(workspacePath, &workspaceNode) - // 添加工作空间节点 + // add workspace node root.FileNodes = append(root.FileNodes, &workspaceNode) } - // 构造 Go API 节点 + // construct Go API node apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg" apiNode := FileNode{Name: "Go API", Path: apiPath, FileNodes: []*FileNode{}} goapiBuildOKSignal := make(chan bool) go func() { apiNode.Type = "d" - // TOOD: Go API 用另外的样式 + // TOOD: Go API use a special style apiNode.IconSkin = "ico-ztree-dir " walk(apiPath, &apiNode) - // 放行信号 + // go-ahead close(goapiBuildOKSignal) }() - // 等待放行 + // waiting <-goapiBuildOKSignal - // 添加 Go API 节点 + // add Go API node root.FileNodes = append(root.FileNodes, &apiNode) data["root"] = root } -// 编辑器打开一个文件. +// GetFile handles request of opening file by editor. func GetFile(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false @@ -110,8 +110,9 @@ func GetFile(w http.ResponseWriter, r *http.Request) { extension := filepath.Ext(path) - // 通过文件扩展名判断是否是图片文件(图片在浏览器里新建 tab 打开) if isImg(extension) { + // image file will be open in a browser tab + data["mode"] = "img" path2 := strings.Replace(path, "\\", "/", -1) @@ -122,15 +123,14 @@ func GetFile(w http.ResponseWriter, r *http.Request) { } isBinary := false - // 判断是否是其他二进制文件 + // determine whether it is a binary file for _, b := range buf { - if 0 == b { // 包含 0 字节就认为是二进制文件 + if 0 == b { isBinary = true } } if isBinary { - // 是二进制文件的话前端编辑器不打开 data["succ"] = false data["msg"] = "Can't open a binary file :(" } else { @@ -140,16 +140,14 @@ func GetFile(w http.ResponseWriter, r *http.Request) { } } -// 保存文件. +// SaveFile handles request of saving file. func SaveFile(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false @@ -157,6 +155,7 @@ func SaveFile(w http.ResponseWriter, r *http.Request) { } filePath := args["file"].(string) + sid := args["sid"].(string) fout, err := os.Create(filePath) @@ -175,20 +174,22 @@ func SaveFile(w http.ResponseWriter, r *http.Request) { glog.Error(err) data["succ"] = false + wSession := session.WideSessions.Get(sid) + wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, + Data: "can't save file " + filePath} + return } } -// 新建文件/目录. +// NewFile handles request of creating file or directory. func NewFile(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false @@ -197,10 +198,16 @@ func NewFile(w http.ResponseWriter, r *http.Request) { path := args["path"].(string) fileType := args["fileType"].(string) + sid := args["sid"].(string) + + wSession := session.WideSessions.Get(sid) if !createFile(path, fileType) { data["succ"] = false + wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, + Data: "can't create file " + path} + return } @@ -210,16 +217,14 @@ func NewFile(w http.ResponseWriter, r *http.Request) { } } -// 删除文件/目录. +// RemoveFile handles request of removing file or directory. func RemoveFile(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false @@ -227,13 +232,19 @@ func RemoveFile(w http.ResponseWriter, r *http.Request) { } path := args["path"].(string) + sid := args["sid"].(string) + + wSession := session.WideSessions.Get(sid) if !removeFile(path) { data["succ"] = false + + wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, + Data: "can't remove file " + path} } } -// 在目录中搜索包含指定字符串的文件. +// SearchText handles request of searching files under the specified directory with the specified keyword. func SearchText(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) @@ -256,7 +267,7 @@ func SearchText(w http.ResponseWriter, r *http.Request) { data["founds"] = founds } -// 遍历指定的路径,构造文件树. +// walk traverses the specified path to build a file tree. func walk(path string, node *FileNode) { files := listFiles(path) @@ -291,7 +302,7 @@ func walk(path string, node *FileNode) { return } -// 列出 dirname 指定目录下的文件/目录名. +// listFiles lists names of files under the specified dirname. func listFiles(dirname string) []string { f, _ := os.Open(dirname) @@ -303,12 +314,12 @@ func listFiles(dirname string) []string { dirs := []string{} files := []string{} - // 排序:目录靠前,文件靠后 + // sort: directories in front of files for _, name := range names { fio, _ := os.Lstat(filepath.Join(dirname, name)) if fio.IsDir() { - // 排除 .git 目录 + // exclude the .git direcitory if ".git" == fio.Name() { continue } @@ -322,9 +333,9 @@ func listFiles(dirname string) []string { return append(dirs, files...) } -// 根据文件后缀获取文件树图标 CSS 类名. +// getIconSkin gets CSS class name of icon with the specified filename extension. // -// CSS 类名可参考 zTree 文档. +// Refers to the zTree document for CSS class names. func getIconSkin(filenameExtension string) string { if isImg(filenameExtension) { return "ico-ztree-img " @@ -354,9 +365,9 @@ func getIconSkin(filenameExtension string) string { } } -// 根据文件后缀获取编辑器 mode. +// getEditorMode gets editor mode with the specified filename extension. // -// 编辑器 mode 可参考 CodeMirror 文档. +// Refers to the CodeMirror document for modes. func getEditorMode(filenameExtension string) string { switch filenameExtension { case ".go": @@ -382,12 +393,12 @@ func getEditorMode(filenameExtension string) string { } } -// 在 path 指定的路径上创建文件. +// createFile creates file on the specified path. // // fileType: // -// "f": 文件 -// "d": 目录 +// "f": file +// "d": directory func createFile(path, fileType string) bool { switch fileType { case "f": @@ -422,7 +433,7 @@ func createFile(path, fileType string) bool { } } -// 删除 path 指定路径的文件或目录. +// removeFile removes file on the specified path. func removeFile(path string) bool { if err := os.RemoveAll(path); nil != err { glog.Errorf("Removes [%s] failed: [%s]", path, err.Error()) @@ -435,7 +446,7 @@ func removeFile(path string) bool { return true } -// 在 dir 指定的目录(包含子目录)中的 extension 指定后缀的文件中搜索包含 text 文本的文件,类似 grep/findstr 命令. +// search finds file under the specified dir and its sub-directories with the specified text, likes the command grep/findstr. func search(dir, extension, text string, snippets []*Snippet) []*Snippet { if !strings.HasSuffix(dir, conf.PathSeparator) { dir += conf.PathSeparator @@ -455,10 +466,10 @@ func search(dir, extension, text string, snippets []*Snippet) []*Snippet { path := dir + fileInfo.Name() if fileInfo.IsDir() { - // 进入目录递归 + // enter the directory recursively snippets = search(path, extension, text, snippets) } else if strings.HasSuffix(path, extension) { - // 在文件中进行搜索 + // grep in file ss := searchInFile(path, text) snippets = append(snippets, ss...) @@ -468,7 +479,7 @@ func search(dir, extension, text string, snippets []*Snippet) []*Snippet { return snippets } -// 在 path 指定的文件内容中搜索 text 指定的文本. +// searchInFile finds file with the specified path and text. func searchInFile(path string, text string) []*Snippet { ret := []*Snippet{} @@ -495,7 +506,7 @@ func searchInFile(path string, text string) []*Snippet { return ret } -// 根据文件名后缀判断是否是图片文件. +// isImg determines whether the specified extension is a image. func isImg(extension string) bool { ext := strings.ToLower(extension) diff --git a/i18n/en_US.json b/i18n/en_US.json index fcc9121..7839ae2 100644 --- a/i18n/en_US.json +++ b/i18n/en_US.json @@ -35,6 +35,7 @@ "unread_notification": "Unread", "notification_2": "Not found [gocode], thereby [Autocomplete] will not work", "notification_3": "Not found [ide_stub], thereby [Jump to Decl], [Find Usages] will not work", + "notification_4": "Server Internal Error", "goto_line": "Goto Line", "go": "Go", "tip": "Tip", diff --git a/i18n/ja_JP.json b/i18n/ja_JP.json index 8a4f009..872c0fc 100644 --- a/i18n/ja_JP.json +++ b/i18n/ja_JP.json @@ -35,6 +35,7 @@ "unread_notification": "未読の通知", "notification_2": "[gocode] が見つかりません。[Autocomplete] は動作しません。", "notification_3": "[ide_stub] が見つかりません。[Jump to Decl]、[Find Usages] は動作しません。", + "notification_4": "内部サーバーエラー", "goto_line": "指定行にジャンプ", "go": "Go", "tip": "ヒント", diff --git a/i18n/zh_CN.json b/i18n/zh_CN.json index acce9aa..059e8e8 100644 --- a/i18n/zh_CN.json +++ b/i18n/zh_CN.json @@ -35,6 +35,7 @@ "unread_notification": "未读通知", "notification_2": "没有检查到 gocode,这将会导致 [自动完成] 失效", "notification_3": "没有检查到 ide_stub,这将会导致 [跳转到声明]、[查找使用] 失效", + "notification_4": "服务器内部错误", "goto_line": "跳转到行", "go": "跳转", "tip": "提示", diff --git a/notification/notifications.go b/notification/notifications.go index d2d3690..a479037 100644 --- a/notification/notifications.go +++ b/notification/notifications.go @@ -3,7 +3,6 @@ package notification import ( "net/http" - "time" "strconv" @@ -21,7 +20,8 @@ const ( Warn = "WARN" // 通知.严重程度:WARN Info = "INFO" // 通知.严重程度:INFO - Setup = "Setup" // 通知.类型:安装 + Setup = "Setup" // 通知.类型:安装 + Server = "Server" // 通知.类型:服务器 ) // 通知结构. @@ -41,16 +41,7 @@ func event2Notification(e *event.Event) { } wsChannel := session.NotificationWS[e.Sid] - - var notification Notification - - switch e.Code { - case event.EvtCodeGocodeNotFound: - notification = Notification{event: e, Type: Setup, Severity: Error} - case event.EvtCodeIDEStubNotFound: - notification = Notification{event: e, Type: Setup, Severity: Error} - default: - glog.Warningf("Can't handle event[code=%d]", e.Code) + if nil == wsChannel { return } @@ -58,13 +49,26 @@ func event2Notification(e *event.Event) { username := httpSession.Values["username"].(string) locale := conf.Wide.GetUser(username).Locale - // 消息国际化处理 - notification.Message = i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string) + var notification *Notification - wsChannel.Conn.WriteJSON(¬ification) + switch e.Code { + case event.EvtCodeGocodeNotFound: + fallthrough + case event.EvtCodeIDEStubNotFound: + notification = &Notification{event: e, Type: Setup, Severity: Error, + Message: i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string)} + case event.EvtCodeServerInternalError: + notification = &Notification{event: e, Type: Server, Severity: Error, + Message: i18n.Get(locale, "notification_"+strconv.Itoa(e.Code)).(string) + " [" + e.Data.(string) + "]"} + default: + glog.Warningf("Can't handle event[code=%d]", e.Code) - // 更新通道最近使用时间 - wsChannel.Time = time.Now() + return + } + + wsChannel.Conn.WriteJSON(notification) + + wsChannel.Refresh() } // 建立通知通道. @@ -101,6 +105,7 @@ func WSHandler(w http.ResponseWriter, r *http.Request) { } glog.Error("Notification WS ERROR: " + err.Error()) + return } } diff --git a/session/users.go b/session/users.go index 2d829f8..99a4143 100644 --- a/session/users.go +++ b/session/users.go @@ -22,11 +22,9 @@ func AddUser(w http.ResponseWriter, r *http.Request) { data := map[string]interface{}{"succ": true} defer util.RetJSON(w, r, data) - decoder := json.NewDecoder(r.Body) - var args map[string]interface{} - if err := decoder.Decode(&args); err != nil { + if err := json.NewDecoder(r.Body).Decode(&args); err != nil { glog.Error(err) data["succ"] = false diff --git a/util/websocket.go b/util/websocket.go index d8dd7e3..6315614 100644 --- a/util/websocket.go +++ b/util/websocket.go @@ -19,3 +19,8 @@ type WSChannel struct { func (c *WSChannel) Close() { c.Conn.Close() } + +// Refresh refreshes the channel by updating its Time. +func (c *WSChannel) Refresh() { + c.Time = time.Now() +}