package service import ( "bytes" "context" "errors" "fmt" "io" "os" "path/filepath" "strings" 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 } 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) } 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 getFs(UserSpacePath string) afero.Fs { bashAbs, _ := filepath.Abs(BASE_PATH) if !strings.HasPrefix(UserSpacePath, "/") { UserSpacePath = "/" + UserSpacePath } return afero.NewBasePathFs(afero.NewOsFs(), bashAbs+UserSpacePath) }