diff --git a/features/file/file.go b/features/file/file.go index 02439fb8..8eaf7fdd 100644 --- a/features/file/file.go +++ b/features/file/file.go @@ -1,12 +1,17 @@ package file import ( + "fmt" "net/http" + "path/filepath" + "ems.agt/lib/dborm" + "ems.agt/lib/file" "ems.agt/lib/log" "ems.agt/lib/services" "ems.agt/restagent/config" "github.com/gorilla/mux" + "github.com/shirou/gopsutil/disk" ) var ( @@ -14,6 +19,12 @@ var ( UriFile = config.DefaultUriPrefix + "/fileManagement/{apiVersion}/{location}/file" CustomUriFile = config.UriPrefix + "/fileManagement/{apiVersion}/{location}/file" + + // 获取磁盘列表 + UriDiskList = config.UriPrefix + "/fileManagement/{apiVersion}/files/diskList" + + // 获取文件列表 + UriListFiles = config.UriPrefix + "/fileManagement/{apiVersion}/files/listFiles" ) // func init() { @@ -89,6 +100,17 @@ func DownloadFile(w http.ResponseWriter, r *http.Request) { return } + // 全路径文件下载 + filePath := r.URL.Query().Get("path") + if location == "path" && filePath != "" { + // 截取文件路径 + dir := filepath.Dir(filePath) + // 截取文件名 + fileName := filepath.Base(filePath) + services.ResponseFileWithNameAndMD5(w, http.StatusOK, fileName, dir, "") + return + } + params := r.URL.Query() fileNames, ok := params["loc"] if !ok { @@ -126,3 +148,46 @@ func DeleteFile(w http.ResponseWriter, r *http.Request) { services.ResponseStatusOK204NoContent(w) return } + +// 磁盘列表 +func DiskList(w http.ResponseWriter, r *http.Request) { + disks := make([]map[string]string, 0) + + partitions, err := disk.Partitions(false) + if err != nil { + services.ResponseWithJson(w, 200, disks) + } + + for _, partition := range partitions { + usage, err := disk.Usage(partition.Mountpoint) + if err != nil { + continue + } + disks = append(disks, map[string]string{ + "size": file.FormatFileSize(float64(usage.Total)), + "used": file.FormatFileSize(float64(usage.Used)), + "avail": file.FormatFileSize(float64(usage.Free)), + "pcent": fmt.Sprintf("%.1f%%", usage.UsedPercent), + "target": partition.Device, + }) + } + services.ResponseWithJson(w, 200, disks) +} + +// 获取文件列表 /files/search +func ListFiles(w http.ResponseWriter, r *http.Request) { + // json 請求參數獲取 + var bodyArgs FileOption + err := services.ShouldBindJSON(r, &bodyArgs) + if err != nil || dborm.DbClient.XEngine == nil { + services.ResponseErrorWithJson(w, 400, err.Error()) + return + } + + files, err := GetFileList(bodyArgs) + if err != nil { + services.ResponseErrorWithJson(w, 400, err.Error()) + return + } + services.ResponseWithJson(w, 200, files) +} diff --git a/features/file/model.go b/features/file/model.go new file mode 100644 index 00000000..1de351f7 --- /dev/null +++ b/features/file/model.go @@ -0,0 +1,85 @@ +package file + +import ( + "bufio" + "fmt" + "io/fs" + "os" + "os/exec" + "time" + + "github.com/spf13/afero" +) + +type FileOption struct { + Path string `json:"path"` + Search string `json:"search"` + ContainSub bool `json:"containSub"` + Expand bool `json:"expand"` + Dir bool `json:"dir"` + ShowHidden bool `json:"showHidden"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} + +type FileInfo struct { + Fs afero.Fs `json:"-"` + Path string `json:"path"` + Name string `json:"name"` + Extension string `json:"extension"` + Content string `json:"content"` + Size int64 `json:"size"` + IsDir bool `json:"isDir"` + IsSymlink bool `json:"isSymlink"` + IsHidden bool `json:"isHidden"` + LinkPath string `json:"linkPath"` + Type string `json:"type"` + Mode string `json:"mode"` + MimeType string `json:"mimeType"` + UpdateTime time.Time `json:"updateTime"` + ModTime time.Time `json:"modTime"` + FileMode os.FileMode `json:"-"` + Items []*FileInfo `json:"items"` + ItemTotal int `json:"itemTotal"` +} + +func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, total int, err error) { + cmd := exec.Command("find", f.Path, "-name", fmt.Sprintf("*%s*", search)) + output, err := cmd.StdoutPipe() + if err != nil { + return + } + if err = cmd.Start(); err != nil { + return + } + defer func() { + _ = cmd.Wait() + _ = cmd.Process.Kill() + }() + + scanner := bufio.NewScanner(output) + for scanner.Scan() { + line := scanner.Text() + info, err := os.Stat(line) + if err != nil { + continue + } + total++ + if total > count { + continue + } + files = append(files, FileSearchInfo{ + Path: line, + FileInfo: info, + }) + } + if err = scanner.Err(); err != nil { + return + } + return +} + +type FileSearchInfo struct { + Path string `json:"path"` + fs.FileInfo +} diff --git a/features/file/service.go b/features/file/service.go new file mode 100644 index 00000000..9fc3ec6e --- /dev/null +++ b/features/file/service.go @@ -0,0 +1,206 @@ +package file + +import ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "ems.agt/lib/file" + "github.com/spf13/afero" +) + +// 获取文件列表 +func GetFileList(op FileOption) (FileInfo, error) { + var fileInfo FileInfo + if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) { + return fileInfo, nil + } + info, err := NewFileInfo(op) + if err != nil { + return fileInfo, err + } + fileInfo = *info + return fileInfo, nil +} + +func NewFileInfo(op FileOption) (*FileInfo, error) { + var appFs = afero.NewOsFs() + + info, err := appFs.Stat(op.Path) + if err != nil { + return nil, err + } + + fileInfo := &FileInfo{ + Fs: appFs, + Path: op.Path, + Name: info.Name(), + IsDir: info.IsDir(), + FileMode: info.Mode(), + ModTime: info.ModTime(), + Size: info.Size(), + IsSymlink: file.IsSymlink(info.Mode()), + Extension: filepath.Ext(info.Name()), + IsHidden: file.IsHidden(op.Path), + Mode: fmt.Sprintf("%04o", info.Mode().Perm()), + MimeType: file.GetMimeType(op.Path), + } + if fileInfo.IsSymlink { + fileInfo.LinkPath = file.GetSymlink(op.Path) + } + if op.Expand { + if fileInfo.IsDir { + if err := listChildren(fileInfo, op.Dir, op.ShowHidden, op.ContainSub, op.Search, op.Page, op.PageSize); err != nil { + return nil, err + } + return fileInfo, nil + } else { + if err := getContent(fileInfo); err != nil { + return nil, err + } + } + } + return fileInfo, nil +} + +func listChildren(f *FileInfo, dir, showHidden, containSub bool, search string, page, pageSize int) error { + afs := &afero.Afero{Fs: f.Fs} + var ( + files []FileSearchInfo + err error + total int + ) + + if search != "" && containSub { + files, total, err = f.search(search, page*pageSize) + if err != nil { + return err + } + } else { + dirFiles, err := afs.ReadDir(f.Path) + if err != nil { + return err + } + for _, file := range dirFiles { + files = append(files, FileSearchInfo{ + Path: f.Path, + FileInfo: file, + }) + } + } + + var items []*FileInfo + for _, df := range files { + if dir && !df.IsDir() { + continue + } + name := df.Name() + fPath := path.Join(df.Path, df.Name()) + if search != "" { + if containSub { + fPath = df.Path + name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/") + } else { + lowerName := strings.ToLower(name) + lowerSearch := strings.ToLower(search) + if !strings.Contains(lowerName, lowerSearch) { + continue + } + } + } + if !showHidden && file.IsHidden(name) { + continue + } + f.ItemTotal++ + isSymlink, isInvalidLink := false, false + if file.IsSymlink(df.Mode()) { + isSymlink = true + info, err := f.Fs.Stat(fPath) + if err == nil { + df.FileInfo = info + } else { + isInvalidLink = true + } + } + + fileInfo := &FileInfo{ + Fs: f.Fs, + Name: name, + Size: df.Size(), + ModTime: df.ModTime(), + FileMode: df.Mode(), + IsDir: df.IsDir(), + IsSymlink: isSymlink, + IsHidden: file.IsHidden(fPath), + Extension: filepath.Ext(name), + Path: fPath, + Mode: fmt.Sprintf("%04o", df.Mode().Perm()), + } + + if isSymlink { + fileInfo.LinkPath = file.GetSymlink(fPath) + } + if df.Size() > 0 { + fileInfo.MimeType = file.GetMimeType(fPath) + } + if isInvalidLink { + fileInfo.Type = "invalid_link" + } + items = append(items, fileInfo) + } + if containSub { + f.ItemTotal = total + } + start := (page - 1) * pageSize + end := pageSize + start + var result []*FileInfo + if start < 0 || start > f.ItemTotal || end < 0 || start > end { + result = items + } else { + if end > f.ItemTotal { + result = items[start:] + } else { + result = items[start:end] + } + } + + f.Items = result + return nil +} + +func getContent(f *FileInfo) error { + if f.Size <= 10*1024*1024 { + afs := &afero.Afero{Fs: f.Fs} + cByte, err := afs.ReadFile(f.Path) + if err != nil { + return nil + } + if len(cByte) > 0 && detectBinary(cByte) { + return errors.New("ErrFileCanNotRead") + } + f.Content = string(cByte) + return nil + } else { + return errors.New("ErrFileCanNotRead") + } +} + +func detectBinary(buf []byte) bool { + whiteByte := 0 + n := 1024 + if len(buf) < 1024 { + n = len(buf) + } + for i := 0; i < n; i++ { + if (buf[i] >= 0x20) || buf[i] == 9 || buf[i] == 10 || buf[i] == 13 { + whiteByte++ + } else if buf[i] <= 6 || (buf[i] >= 14 && buf[i] <= 31) { + return true + } + } + + return whiteByte < 1 +}