287 lines
7.4 KiB
Go
287 lines
7.4 KiB
Go
package service
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
|
||
"dubbo.apache.org/dubbo-go/v3/common/logger"
|
||
filesApi "github.com/filebrowser/filebrowser/v2/api/files"
|
||
"github.com/filebrowser/filebrowser/v2/files"
|
||
"github.com/filebrowser/filebrowser/v2/rules"
|
||
"github.com/filebrowser/filebrowser/v2/search"
|
||
"github.com/spf13/afero"
|
||
)
|
||
|
||
type FilesProvider struct {
|
||
filesApi.UnimplementedFileServer
|
||
}
|
||
|
||
const BASE_PATH = "../"
|
||
|
||
func (f *FilesProvider) List(ctx context.Context, req *filesApi.FileListReq) (*filesApi.FileListResp, error) {
|
||
file, err := files.NewFileInfo(&files.FileOptions{
|
||
Fs: getFs(req.UserSpacePath),
|
||
Path: req.Path,
|
||
Modify: true,
|
||
Expand: true,
|
||
ReadHeader: true,
|
||
Content: true,
|
||
Checker: rules.EmptyChecker,
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if !file.IsDir {
|
||
return nil, errors.New("路径不是文件夹")
|
||
}
|
||
file.Listing.Sorting.Asc = req.Sorting.Asc
|
||
file.Listing.Sorting.By = req.Sorting.By
|
||
file.Listing.ApplySort()
|
||
result := &filesApi.FileListResp{
|
||
NumDirs: int32(file.NumDirs),
|
||
NumFiles: int32(file.NumFiles),
|
||
Sorting: &filesApi.Sorting{
|
||
By: file.Sorting.By,
|
||
Asc: file.Sorting.Asc,
|
||
},
|
||
Path: file.Path,
|
||
Name: file.Name,
|
||
Size: file.Size,
|
||
Extension: file.Extension,
|
||
Mode: file.Mode.String(),
|
||
IsDir: file.IsDir,
|
||
IsSymlink: file.IsSymlink,
|
||
Type: file.Type,
|
||
Items: make([]*filesApi.Items, len(file.Listing.Items)),
|
||
}
|
||
for _, v := range file.Listing.Items {
|
||
result.Items = append(result.Items, &filesApi.Items{
|
||
Path: v.Path,
|
||
Name: v.Name,
|
||
Size: v.Size,
|
||
Extension: v.Extension,
|
||
Mode: v.Mode.String(),
|
||
IsDir: v.IsDir,
|
||
IsSymlink: v.IsSymlink,
|
||
Type: v.Type,
|
||
})
|
||
}
|
||
return result, err
|
||
}
|
||
|
||
func (f *FilesProvider) Create(ctx context.Context, req *filesApi.CreateReq) (*filesApi.CreateResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
return new(filesApi.CreateResp), fs.MkdirAll(req.Path, files.PermDir)
|
||
}
|
||
|
||
func (f *FilesProvider) Delete(ctx context.Context, req *filesApi.DeleteReq) (*filesApi.DeleteResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
return new(filesApi.DeleteResp), fs.RemoveAll(req.Path)
|
||
}
|
||
|
||
func (f *FilesProvider) Upload(ctx context.Context, req *filesApi.UploadReq) (*filesApi.UploadResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
fi, err := fs.Create(req.Path)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer fi.Close()
|
||
_, err = fi.Write(req.Content)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return new(filesApi.UploadResp), nil
|
||
}
|
||
|
||
func (f *FilesProvider) Search(ctx context.Context, req *filesApi.SearchReq) (*filesApi.SearchResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
result := new(filesApi.SearchResp)
|
||
err := search.Search(fs, req.Path, req.Query, rules.EmptyChecker, func(path string, f os.FileInfo) error {
|
||
result.Items = append(result.Items, &filesApi.SearchResp_Nested{
|
||
Dir: f.IsDir(),
|
||
Path: path,
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return result, err
|
||
}
|
||
|
||
func (f *FilesProvider) TusCreate(ctx context.Context, req *filesApi.TusCreateReq) (*filesApi.TusCreateResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
file, err := files.NewFileInfo(&files.FileOptions{
|
||
Fs: fs,
|
||
Path: req.Path,
|
||
Modify: true,
|
||
Expand: false,
|
||
ReadHeader: true,
|
||
Checker: rules.EmptyChecker,
|
||
})
|
||
switch {
|
||
case errors.Is(err, afero.ErrFileNotFound):
|
||
dirPath := filepath.Dir(req.Path)
|
||
if _, statErr := fs.Stat(dirPath); os.IsNotExist(statErr) {
|
||
if mkdirErr := fs.MkdirAll(dirPath, files.PermDir); mkdirErr != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
case err != nil:
|
||
return nil, err
|
||
}
|
||
fileFlags := os.O_CREATE | os.O_WRONLY
|
||
if req.Override {
|
||
fileFlags |= os.O_TRUNC
|
||
}
|
||
|
||
// if file exists
|
||
if file != nil {
|
||
if file.IsDir {
|
||
return nil, fmt.Errorf("上传路径不能是文件夹 %s", file.RealPath())
|
||
}
|
||
}
|
||
|
||
openFile, err := fs.OpenFile(req.Path, fileFlags, files.PermFile)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := openFile.Close(); err != nil {
|
||
return nil, err
|
||
}
|
||
logger.Infof("创建空文件,%v", openFile.Name())
|
||
return &filesApi.TusCreateResp{
|
||
UploadLength: -1,
|
||
UploadOffset: 0,
|
||
}, nil
|
||
}
|
||
|
||
func (f *FilesProvider) TusUpload(ctx context.Context, req *filesApi.TusUploadReq) (*filesApi.TusUploadResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
file, err := files.NewFileInfo(&files.FileOptions{
|
||
Fs: fs,
|
||
Path: req.Path,
|
||
Modify: true,
|
||
Expand: false,
|
||
ReadHeader: true,
|
||
Checker: rules.EmptyChecker,
|
||
})
|
||
switch {
|
||
case errors.Is(err, afero.ErrFileNotFound):
|
||
return nil, errors.New("未找到文件")
|
||
case err != nil:
|
||
return nil, err
|
||
}
|
||
|
||
switch {
|
||
case file.IsDir:
|
||
return nil, fmt.Errorf("上传路径不能是文件夹 %s", file.RealPath())
|
||
case file.Size != req.UploadOffset:
|
||
return nil, fmt.Errorf(
|
||
"%s 文件大小符合: %d",
|
||
file.RealPath(),
|
||
req.UploadOffset,
|
||
)
|
||
}
|
||
openFile, err := fs.OpenFile(req.Path, os.O_WRONLY|os.O_APPEND, files.PermFile)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("could not open file: %w", err)
|
||
}
|
||
defer openFile.Close()
|
||
|
||
_, err = openFile.Seek(req.UploadOffset, 0)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("could not seek file: %w", err)
|
||
}
|
||
|
||
logger.Infof("写入文件块,offset:%v,块大小:%vbyte", req.UploadOffset, len(req.Content))
|
||
bytesWritten, err := io.Copy(openFile, bytes.NewBuffer(req.Content))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("could not write to file: %w", err)
|
||
}
|
||
return &filesApi.TusUploadResp{
|
||
UploadOffset: bytesWritten,
|
||
}, nil
|
||
}
|
||
|
||
func (f *FilesProvider) Info(ctx context.Context, req *filesApi.FileInfoReq) (*filesApi.FileInfoResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
file, err := files.NewFileInfo(&files.FileOptions{
|
||
Fs: fs,
|
||
Path: req.Path,
|
||
Modify: true,
|
||
Expand: false,
|
||
ReadHeader: true,
|
||
Checker: rules.EmptyChecker,
|
||
})
|
||
switch {
|
||
case errors.Is(err, afero.ErrFileNotFound):
|
||
return nil, errors.New("未找到文件")
|
||
case err != nil:
|
||
return nil, err
|
||
}
|
||
result := &filesApi.FileInfoResp{
|
||
Path: file.Path,
|
||
Name: file.Name,
|
||
Size: file.Size,
|
||
Extension: file.Extension,
|
||
Mode: file.Mode.String(),
|
||
IsDir: file.IsDir,
|
||
IsSymlink: file.IsSymlink,
|
||
Type: file.Type,
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
func (f *FilesProvider) ResumableTransfer(ctx context.Context, req *filesApi.ResumableTransferReq) (*filesApi.ResumableTransferResp, error) {
|
||
fs := getFs(req.UserSpacePath)
|
||
_, err := files.NewFileInfo(&files.FileOptions{
|
||
Fs: fs,
|
||
Path: req.Path,
|
||
Modify: true,
|
||
Expand: false,
|
||
ReadHeader: true,
|
||
Checker: rules.EmptyChecker,
|
||
})
|
||
switch {
|
||
case errors.Is(err, afero.ErrFileNotFound):
|
||
return nil, errors.New("未找到文件")
|
||
case err != nil:
|
||
return nil, err
|
||
}
|
||
openFile, err := fs.Open(req.Path)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("could not open file: %w", err)
|
||
}
|
||
defer openFile.Close()
|
||
logger.Debugf("设置文件读取,offset:%v,length:%v", req.Offset, req.Length)
|
||
_, err = openFile.Seek(req.Offset, 0)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
b := make([]byte, req.Length)
|
||
n, err := openFile.Read(b)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &filesApi.ResumableTransferResp{
|
||
Content: b[:n],
|
||
}, nil
|
||
}
|
||
|
||
func getFs(UserSpacePath string) afero.Fs {
|
||
bashAbs, _ := filepath.Abs(BASE_PATH)
|
||
if !strings.HasPrefix(UserSpacePath, "/") {
|
||
UserSpacePath = "/" + UserSpacePath
|
||
}
|
||
return afero.NewBasePathFs(afero.NewOsFs(), bashAbs+UserSpacePath)
|
||
}
|