micro-filebrowser/service/files.go
2025-05-23 11:31:46 +08:00

287 lines
7.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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:%vlength%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)
}