wide/file/files.go

531 lines
12 KiB
Go
Raw Normal View History

2014-10-28 19:04:46 +03:00
// File tree manipulations.
2014-08-18 17:45:43 +04:00
package file
import (
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path/filepath"
2014-09-12 06:56:18 +04:00
"runtime"
2014-08-18 17:45:43 +04:00
"sort"
"strings"
2014-09-07 13:31:57 +04:00
"github.com/b3log/wide/conf"
2014-10-28 19:04:46 +03:00
"github.com/b3log/wide/event"
2014-09-17 10:35:48 +04:00
"github.com/b3log/wide/session"
2014-09-07 13:31:57 +04:00
"github.com/b3log/wide/util"
"github.com/golang/glog"
2014-08-18 17:45:43 +04:00
)
2014-10-28 19:04:46 +03:00
// File node, used to construct the file tree.
2014-10-11 10:08:29 +04:00
type FileNode struct {
Name string `json:"name"`
2014-11-05 10:24:58 +03:00
Title string `json:"title"`
2014-10-11 10:08:29 +04:00
Path string `json:"path"`
2014-11-06 10:52:52 +03:00
IconSkin string `json:"iconSkin"` // Value should be end with a space
Type string `json:"type"` // "f": file, "d": directory
Creatable bool `json:"creatable"` // whether can create file in this file node
Removable bool `json:"removable"` // whether can remove this file node
2014-10-11 10:08:29 +04:00
Mode string `json:"mode"`
FileNodes []*FileNode `json:"children"`
}
2014-10-28 19:37:47 +03:00
// Source code snippet, used to as the result of "Find Usages", "Search".
2014-10-11 10:08:29 +04:00
type Snippet struct {
2014-10-28 19:37:47 +03:00
Path string `json:"path"` // file path
Line int `json:"line"` // line number
Ch int `json:"ch"` // column number
Contents []string `json:"contents"` // lines nearby
2014-10-11 10:08:29 +04:00
}
2014-10-28 19:37:47 +03:00
// GetFiles handles request of constructing user workspace file tree.
2014-09-25 13:34:05 +04:00
//
2014-10-28 19:37:47 +03:00
// 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.
2014-08-18 17:45:43 +04:00
func GetFiles(w http.ResponseWriter, r *http.Request) {
2014-09-01 16:50:51 +04:00
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
2014-09-17 10:35:48 +04:00
session, _ := session.HTTPSession.Get(r, "wide-session")
2014-08-31 10:15:13 +04:00
2014-08-31 14:50:38 +04:00
username := session.Values["username"].(string)
2014-10-26 09:46:15 +03:00
userWorkspace := conf.Wide.GetUserWorkspace(username)
2014-10-28 08:22:39 +03:00
workspaces := filepath.SplitList(userWorkspace)
2014-08-31 10:15:13 +04:00
2014-10-26 09:46:15 +03:00
root := FileNode{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", FileNodes: []*FileNode{}}
2014-10-28 19:37:47 +03:00
// workspace node process
2014-10-26 09:46:15 +03:00
for _, workspace := range workspaces {
workspacePath := workspace + conf.PathSeparator + "src"
2014-11-05 10:24:58 +03:00
workspaceNode := FileNode{Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:],
2014-11-06 10:52:52 +03:00
Title: workspace, Path: workspacePath, IconSkin: "ico-ztree-dir-workspace ", Type: "d",
Creatable: true, Removable: false, FileNodes: []*FileNode{}}
2014-10-26 09:46:15 +03:00
2014-11-06 12:06:53 +03:00
walk(workspacePath, &workspaceNode, true, true)
2014-10-26 09:46:15 +03:00
2014-10-28 19:37:47 +03:00
// add workspace node
2014-10-26 09:46:15 +03:00
root.FileNodes = append(root.FileNodes, &workspaceNode)
}
2014-08-18 17:45:43 +04:00
2014-10-28 19:37:47 +03:00
// construct Go API node
2014-10-13 10:34:42 +04:00
apiPath := runtime.GOROOT() + conf.PathSeparator + "src" + conf.PathSeparator + "pkg"
2014-11-06 10:52:52 +03:00
apiNode := FileNode{Name: "Go API", Title: apiPath, Path: apiPath, IconSkin: "ico-ztree-dir-api ", Type: "d",
2014-11-06 12:06:53 +03:00
Creatable: false, Removable: false, FileNodes: []*FileNode{}}
2014-09-16 17:20:12 +04:00
goapiBuildOKSignal := make(chan bool)
2014-09-12 06:56:18 +04:00
go func() {
2014-11-06 12:06:53 +03:00
walk(apiPath, &apiNode, false, false)
2014-09-16 17:20:12 +04:00
2014-10-28 19:37:47 +03:00
// go-ahead
2014-09-16 17:20:12 +04:00
close(goapiBuildOKSignal)
2014-09-12 06:56:18 +04:00
}()
2014-10-28 19:37:47 +03:00
// waiting
2014-09-16 17:20:12 +04:00
<-goapiBuildOKSignal
2014-10-28 19:37:47 +03:00
// add Go API node
2014-09-12 06:56:18 +04:00
root.FileNodes = append(root.FileNodes, &apiNode)
2014-08-18 17:45:43 +04:00
data["root"] = root
}
2014-10-28 19:37:47 +03:00
// GetFile handles request of opening file by editor.
2014-08-18 17:45:43 +04:00
func GetFile(w http.ResponseWriter, r *http.Request) {
2014-09-01 14:55:11 +04:00
data := map[string]interface{}{"succ": true}
2014-09-01 16:50:51 +04:00
defer util.RetJSON(w, r, data)
2014-09-01 14:55:11 +04:00
2014-08-18 17:45:43 +04:00
var args map[string]interface{}
2014-10-28 16:32:19 +03:00
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
2014-08-18 17:45:43 +04:00
glog.Error(err)
2014-09-01 16:50:51 +04:00
data["succ"] = false
2014-08-18 17:45:43 +04:00
return
}
path := args["path"].(string)
buf, _ := ioutil.ReadFile(path)
2014-09-05 17:10:28 +04:00
extension := filepath.Ext(path)
2014-09-01 17:40:10 +04:00
if isImg(extension) {
2014-10-28 19:37:47 +03:00
// image file will be open in a browser tab
2014-09-01 17:40:10 +04:00
data["mode"] = "img"
path2 := strings.Replace(path, "\\", "/", -1)
2014-09-05 17:10:28 +04:00
idx := strings.Index(path2, "/data/user_workspaces")
2014-09-01 17:40:10 +04:00
data["path"] = path2[idx:]
return
}
2014-10-31 08:15:40 +03:00
content := string(buf)
2014-09-01 14:55:11 +04:00
2014-10-31 08:15:40 +03:00
if isBinary(content) {
2014-09-01 14:55:11 +04:00
data["succ"] = false
data["msg"] = "Can't open a binary file :("
} else {
2014-10-31 08:15:40 +03:00
data["content"] = content
2014-09-01 14:55:11 +04:00
data["mode"] = getEditorMode(extension)
2014-09-13 09:05:50 +04:00
data["path"] = path
2014-08-18 17:45:43 +04:00
}
}
2014-10-28 19:37:47 +03:00
// SaveFile handles request of saving file.
2014-08-18 17:45:43 +04:00
func SaveFile(w http.ResponseWriter, r *http.Request) {
2014-09-01 16:50:51 +04:00
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
2014-08-18 17:45:43 +04:00
var args map[string]interface{}
2014-10-28 16:32:19 +03:00
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
2014-08-18 17:45:43 +04:00
glog.Error(err)
2014-09-01 16:50:51 +04:00
data["succ"] = false
2014-08-18 17:45:43 +04:00
return
}
filePath := args["file"].(string)
2014-10-29 04:29:12 +03:00
sid := args["sid"].(string)
2014-08-18 17:45:43 +04:00
fout, err := os.Create(filePath)
if nil != err {
glog.Error(err)
2014-09-01 16:50:51 +04:00
data["succ"] = false
2014-08-18 17:45:43 +04:00
return
}
code := args["code"].(string)
fout.WriteString(code)
if err := fout.Close(); nil != err {
glog.Error(err)
2014-09-01 16:50:51 +04:00
data["succ"] = false
2014-08-18 17:45:43 +04:00
2014-10-29 04:29:12 +03:00
wSession := session.WideSessions.Get(sid)
2014-10-28 19:37:47 +03:00
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't save file " + filePath}
2014-08-18 17:45:43 +04:00
return
}
}
2014-10-28 19:04:46 +03:00
// NewFile handles request of creating file or directory.
2014-08-18 17:45:43 +04:00
func NewFile(w http.ResponseWriter, r *http.Request) {
2014-09-01 16:50:51 +04:00
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
2014-08-18 17:45:43 +04:00
var args map[string]interface{}
2014-10-28 16:32:19 +03:00
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
2014-08-18 17:45:43 +04:00
glog.Error(err)
2014-09-01 16:50:51 +04:00
data["succ"] = false
2014-08-18 17:45:43 +04:00
return
}
path := args["path"].(string)
fileType := args["fileType"].(string)
2014-10-28 19:04:46 +03:00
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
2014-08-18 17:45:43 +04:00
if !createFile(path, fileType) {
data["succ"] = false
2014-09-22 10:20:17 +04:00
2014-10-28 19:04:46 +03:00
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't create file " + path}
2014-09-22 10:20:17 +04:00
return
}
if "f" == fileType {
extension := filepath.Ext(path)
data["mode"] = getEditorMode(extension)
2014-08-18 17:45:43 +04:00
}
}
2014-10-28 19:04:46 +03:00
// RemoveFile handles request of removing file or directory.
2014-08-18 17:45:43 +04:00
func RemoveFile(w http.ResponseWriter, r *http.Request) {
2014-09-01 16:50:51 +04:00
data := map[string]interface{}{"succ": true}
defer util.RetJSON(w, r, data)
2014-08-18 17:45:43 +04:00
var args map[string]interface{}
2014-10-28 16:32:19 +03:00
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
2014-08-18 17:45:43 +04:00
glog.Error(err)
2014-09-01 16:50:51 +04:00
data["succ"] = false
2014-08-18 17:45:43 +04:00
return
}
path := args["path"].(string)
2014-10-29 04:29:12 +03:00
sid := args["sid"].(string)
wSession := session.WideSessions.Get(sid)
2014-08-18 17:45:43 +04:00
if !removeFile(path) {
data["succ"] = false
2014-10-28 19:37:47 +03:00
wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid,
Data: "can't remove file " + path}
2014-08-18 17:45:43 +04:00
}
}
2014-10-28 19:04:46 +03:00
// SearchText handles request of searching files under the specified directory with the specified keyword.
2014-10-11 10:59:38 +04:00
func SearchText(w http.ResponseWriter, r *http.Request) {
2014-10-11 10:08:29 +04:00
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
}
dir := args["dir"].(string)
2014-10-11 10:59:38 +04:00
extension := args["extension"].(string)
text := args["text"].(string)
2014-10-11 10:08:29 +04:00
2014-10-11 10:59:38 +04:00
founds := search(dir, extension, text, []*Snippet{})
2014-10-11 10:08:29 +04:00
data["founds"] = founds
2014-08-18 17:45:43 +04:00
}
2014-10-28 19:37:47 +03:00
// walk traverses the specified path to build a file tree.
2014-11-06 12:06:53 +03:00
func walk(path string, node *FileNode, creatable, removable bool) {
2014-08-18 17:45:43 +04:00
files := listFiles(path)
for _, filename := range files {
fpath := filepath.Join(path, filename)
fio, _ := os.Lstat(fpath)
2014-11-06 12:06:53 +03:00
child := FileNode{Name: filename, Title: fpath, Path: fpath, Removable: removable, FileNodes: []*FileNode{}}
2014-08-18 17:45:43 +04:00
node.FileNodes = append(node.FileNodes, &child)
if nil == fio {
glog.Warningf("Path [%s] is nil", fpath)
continue
}
if fio.IsDir() {
child.Type = "d"
2014-11-06 12:06:53 +03:00
child.Creatable = creatable
2014-09-05 13:39:21 +04:00
child.IconSkin = "ico-ztree-dir "
2014-08-23 19:06:23 +04:00
2014-11-06 12:06:53 +03:00
walk(fpath, &child, creatable, removable)
2014-08-18 17:45:43 +04:00
} else {
child.Type = "f"
2014-11-06 12:06:53 +03:00
child.Creatable = creatable
2014-09-05 17:10:28 +04:00
ext := filepath.Ext(fpath)
2014-09-05 13:01:20 +04:00
2014-09-05 17:10:28 +04:00
child.IconSkin = getIconSkin(ext)
2014-09-07 13:31:57 +04:00
child.Mode = getEditorMode(ext)
2014-08-18 17:45:43 +04:00
}
}
return
}
2014-10-28 19:37:47 +03:00
// listFiles lists names of files under the specified dirname.
2014-08-18 17:45:43 +04:00
func listFiles(dirname string) []string {
f, _ := os.Open(dirname)
names, _ := f.Readdirnames(-1)
f.Close()
sort.Strings(names)
2014-08-23 19:06:23 +04:00
dirs := []string{}
files := []string{}
2014-10-28 19:37:47 +03:00
// sort: directories in front of files
2014-08-23 19:06:23 +04:00
for _, name := range names {
fio, _ := os.Lstat(filepath.Join(dirname, name))
if fio.IsDir() {
2014-10-28 19:37:47 +03:00
// exclude the .git direcitory
2014-08-31 10:07:35 +04:00
if ".git" == fio.Name() {
continue
}
2014-08-23 19:06:23 +04:00
dirs = append(dirs, name)
} else {
files = append(files, name)
}
}
return append(dirs, files...)
2014-08-18 17:45:43 +04:00
}
2014-10-28 19:37:47 +03:00
// getIconSkin gets CSS class name of icon with the specified filename extension.
2014-09-25 13:34:05 +04:00
//
2014-10-28 19:37:47 +03:00
// Refers to the zTree document for CSS class names.
2014-09-05 13:01:20 +04:00
func getIconSkin(filenameExtension string) string {
2014-09-16 13:54:50 +04:00
if isImg(filenameExtension) {
return "ico-ztree-img "
}
2014-09-05 13:01:20 +04:00
switch filenameExtension {
2014-09-16 13:54:50 +04:00
case ".html", ".htm":
return "ico-ztree-html "
case ".go":
return "ico-ztree-go "
case ".css":
return "ico-ztree-css "
2014-09-05 13:01:20 +04:00
case ".txt":
2014-09-05 13:39:21 +04:00
return "ico-ztree-text "
2014-09-16 13:54:50 +04:00
case ".sql":
return "ico-ztree-sql "
2014-09-05 13:01:20 +04:00
case ".properties":
2014-09-05 13:39:21 +04:00
return "ico-ztree-pro "
2014-09-16 13:54:50 +04:00
case ".md":
return "ico-ztree-md "
2014-09-20 09:21:38 +04:00
case ".js", ".json":
2014-09-16 13:54:50 +04:00
return "ico-ztree-js "
case ".xml":
return "ico-ztree-xml "
2014-09-05 13:01:20 +04:00
default:
2014-09-16 13:54:50 +04:00
return "ico-ztree-other "
2014-09-05 13:01:20 +04:00
}
}
2014-10-28 19:37:47 +03:00
// getEditorMode gets editor mode with the specified filename extension.
2014-09-25 13:34:05 +04:00
//
2014-10-28 19:37:47 +03:00
// Refers to the CodeMirror document for modes.
2014-08-18 17:45:43 +04:00
func getEditorMode(filenameExtension string) string {
switch filenameExtension {
case ".go":
2014-09-06 18:33:09 +04:00
return "text/x-go"
2014-08-18 17:45:43 +04:00
case ".html":
2014-09-06 18:33:09 +04:00
return "text/html"
2014-08-18 17:45:43 +04:00
case ".md":
2014-09-06 18:33:09 +04:00
return "text/x-markdown"
2014-09-06 18:20:10 +04:00
case ".js":
2014-09-06 18:33:09 +04:00
return "text/javascript"
2014-09-06 18:20:10 +04:00
case ".json":
return "application/json"
2014-08-18 17:45:43 +04:00
case ".css":
2014-09-06 18:33:09 +04:00
return "text/css"
2014-08-18 17:45:43 +04:00
case ".xml":
2014-09-06 18:33:09 +04:00
return "application/xml"
2014-08-18 17:45:43 +04:00
case ".sh":
2014-09-06 18:33:09 +04:00
return "text/x-sh"
2014-08-18 17:45:43 +04:00
case ".sql":
2014-09-06 18:33:09 +04:00
return "text/x-sql"
2014-08-18 17:45:43 +04:00
default:
2014-09-06 18:33:09 +04:00
return "text/plain"
2014-08-18 17:45:43 +04:00
}
}
2014-10-28 19:37:47 +03:00
// createFile creates file on the specified path.
2014-09-25 13:34:05 +04:00
//
// fileType:
//
2014-10-28 19:37:47 +03:00
// "f": file
// "d": directory
2014-08-18 17:45:43 +04:00
func createFile(path, fileType string) bool {
switch fileType {
case "f":
file, err := os.OpenFile(path, os.O_CREATE, 0664)
if nil != err {
2014-10-26 10:59:12 +03:00
glog.Error(err)
2014-08-18 17:45:43 +04:00
return false
}
defer file.Close()
2014-10-26 10:59:12 +03:00
glog.V(5).Infof("Created file [%s]", path)
2014-08-18 17:45:43 +04:00
return true
case "d":
err := os.Mkdir(path, 0775)
if nil != err {
2014-10-26 10:59:12 +03:00
glog.Error(err)
return false
2014-08-18 17:45:43 +04:00
}
2014-10-26 10:59:12 +03:00
glog.V(5).Infof("Created directory [%s]", path)
2014-08-18 17:45:43 +04:00
return true
default:
2014-10-26 10:59:12 +03:00
glog.Errorf("Unsupported file type [%s]", fileType)
2014-08-18 17:45:43 +04:00
return false
}
}
2014-10-28 19:37:47 +03:00
// removeFile removes file on the specified path.
2014-08-18 17:45:43 +04:00
func removeFile(path string) bool {
if err := os.RemoveAll(path); nil != err {
glog.Errorf("Removes [%s] failed: [%s]", path, err.Error())
return false
}
glog.Infof("Removed [%s]", path)
return true
}
2014-09-01 17:40:10 +04:00
2014-10-28 19:37:47 +03:00
// search finds file under the specified dir and its sub-directories with the specified text, likes the command grep/findstr.
2014-10-11 10:08:29 +04:00
func search(dir, extension, text string, snippets []*Snippet) []*Snippet {
2014-10-13 10:34:42 +04:00
if !strings.HasSuffix(dir, conf.PathSeparator) {
dir += conf.PathSeparator
2014-10-11 11:29:21 +04:00
}
2014-10-11 10:08:29 +04:00
f, _ := os.Open(dir)
2014-10-11 10:45:44 +04:00
fileInfos, err := f.Readdir(-1)
2014-10-11 10:08:29 +04:00
f.Close()
if nil != err {
glog.Errorf("Read dir [%s] failed: [%s]", dir, err.Error())
return snippets
}
2014-10-11 10:45:44 +04:00
for _, fileInfo := range fileInfos {
2014-10-11 11:29:21 +04:00
path := dir + fileInfo.Name()
2014-10-11 10:45:44 +04:00
2014-10-11 11:29:21 +04:00
if fileInfo.IsDir() {
2014-10-28 19:37:47 +03:00
// enter the directory recursively
2014-10-11 11:29:21 +04:00
snippets = search(path, extension, text, snippets)
} else if strings.HasSuffix(path, extension) {
2014-10-28 19:37:47 +03:00
// grep in file
2014-10-11 10:45:44 +04:00
ss := searchInFile(path, text)
snippets = append(snippets, ss...)
}
}
2014-10-11 10:08:29 +04:00
return snippets
}
2014-10-28 19:37:47 +03:00
// searchInFile finds file with the specified path and text.
2014-10-11 10:45:44 +04:00
func searchInFile(path string, text string) []*Snippet {
ret := []*Snippet{}
bytes, err := ioutil.ReadFile(path)
if nil != err {
glog.Errorf("Read file [%s] failed: [%s]", path, err.Error())
return ret
}
content := string(bytes)
2014-10-31 08:15:40 +03:00
if isBinary(content) {
return ret
}
2014-10-11 10:45:44 +04:00
lines := strings.Split(content, "\n")
for idx, line := range lines {
ch := strings.Index(line, text)
if -1 != ch {
snippet := &Snippet{Path: path, Line: idx + 1, Ch: ch + 1, Contents: []string{line}}
ret = append(ret, snippet)
}
}
return ret
}
2014-10-31 08:15:40 +03:00
// isBinary determines whether the specified content is a binary file content.
func isBinary(content string) bool {
for _, b := range content {
if 0 == b {
return true
}
}
return false
}
2014-10-28 19:37:47 +03:00
// isImg determines whether the specified extension is a image.
2014-09-01 17:40:10 +04:00
func isImg(extension string) bool {
ext := strings.ToLower(extension)
switch ext {
case ".jpg", ".jpeg", ".bmp", ".gif", ".png", ".svg", ".ico":
return true
default:
return false
}
}