From e8a6df9a86ffc09df7385501db7c76ac8fb05b3c Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Sat, 2 May 2020 10:22:28 +0800 Subject: [PATCH] Feat: import file from existing outer folder --- assets | 2 +- models/policy.go | 5 +++ models/policy_test.go | 2 ++ pkg/filesystem/manage.go | 32 +++++++++++++++++ pkg/filesystem/manage_test.go | 48 ++++++++++++++++++++++++++ pkg/filesystem/upload_test.go | 3 +- routers/controllers/admin.go | 11 ++++++ routers/router.go | 3 ++ service/admin/file.go | 65 +++++++++++++++++++++++++++++++++++ 9 files changed, 169 insertions(+), 2 deletions(-) diff --git a/assets b/assets index 10e5497..df766e4 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 10e5497cfb9f2c180f28a5a492745f521c4b72b1 +Subproject commit df766e44387528370bfa913b094ad16898a013a2 diff --git a/models/policy.go b/models/policy.go index 2aa248f..a4d4181 100644 --- a/models/policy.go +++ b/models/policy.go @@ -222,6 +222,11 @@ func (policy *Policy) IsThumbGenerateNeeded() bool { return policy.Type == "local" } +// CanStructureBeListed 返回存储策略是否能被前台列物理目录 +func (policy *Policy) CanStructureBeListed() bool { + return policy.Type != "local" && policy.Type != "remote" +} + // GetUploadURL 获取文件上传服务API地址 func (policy *Policy) GetUploadURL() string { server, err := url.Parse(policy.Server) diff --git a/models/policy_test.go b/models/policy_test.go index be7193f..a079d8e 100644 --- a/models/policy_test.go +++ b/models/policy_test.go @@ -246,9 +246,11 @@ func TestPolicy_Props(t *testing.T) { asserts.True(policy.IsPathGenerateNeeded()) asserts.True(policy.IsTransitUpload(4)) asserts.False(policy.IsTransitUpload(5 * 1024 * 1024)) + asserts.True(policy.CanStructureBeListed()) policy.Type = "local" asserts.True(policy.IsThumbGenerateNeeded()) asserts.True(policy.IsPathGenerateNeeded()) + asserts.False(policy.CanStructureBeListed()) } func TestPolicy_IsThumbExist(t *testing.T) { diff --git a/pkg/filesystem/manage.go b/pkg/filesystem/manage.go index 018f036..21542f8 100644 --- a/pkg/filesystem/manage.go +++ b/pkg/filesystem/manage.go @@ -285,6 +285,38 @@ func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor fu return fs.listObjects(ctx, parentPath, childFiles, childFolders, pathProcessor), nil } +// ListPhysical 列出存储策略中的外部目录 +// TODO:测试 +func (fs *FileSystem) ListPhysical(ctx context.Context, dirPath string) ([]Object, error) { + if err := fs.DispatchHandler(); fs.Policy == nil || err != nil { + return nil, ErrUnknownPolicyType + } + + // 存储策略不支持列取时,返回空结果 + if !fs.Policy.CanStructureBeListed() { + return nil, nil + } + + // 列取路径 + objects, err := fs.Handler.List(ctx, dirPath, false) + if err != nil { + return nil, err + } + + var ( + folders []model.Folder + ) + for _, object := range objects { + if object.IsDir { + folders = append(folders, model.Folder{ + Name: object.Name, + }) + } + } + + return fs.listObjects(ctx, dirPath, nil, folders, nil), nil +} + func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []model.File, folders []model.Folder, pathProcessor func(string) string) []Object { // 分享文件的ID shareKey := "" diff --git a/pkg/filesystem/manage_test.go b/pkg/filesystem/manage_test.go index 53e18a4..5ef221d 100644 --- a/pkg/filesystem/manage_test.go +++ b/pkg/filesystem/manage_test.go @@ -8,14 +8,62 @@ import ( "github.com/HFO4/cloudreve/pkg/cache" "github.com/HFO4/cloudreve/pkg/conf" "github.com/HFO4/cloudreve/pkg/filesystem/fsctx" + "github.com/HFO4/cloudreve/pkg/filesystem/response" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" + testMock "github.com/stretchr/testify/mock" "os" "testing" ) +func TestFileSystem_ListPhysical(t *testing.T) { + asserts := assert.New(t) + fs := &FileSystem{ + User: &model.User{ + Model: gorm.Model{ + ID: 1, + }, + }, + Policy: &model.Policy{Type: "mock"}, + } + ctx := context.Background() + + // 未知存储策略 + { + fs.Policy.Type = "unknown" + res, err := fs.ListPhysical(ctx, "/") + asserts.Equal(ErrUnknownPolicyType, err) + asserts.Empty(res) + fs.Policy.Type = "mock" + } + + // 无法列取目录 + { + testHandler := new(FileHeaderMock) + testHandler.On("List", testMock.Anything, "/", testMock.Anything).Return([]response.Object{}, errors.New("error")) + fs.Handler = testHandler + res, err := fs.ListPhysical(ctx, "/") + asserts.EqualError(err, "error") + asserts.Empty(res) + } + + // 成功 + { + testHandler := new(FileHeaderMock) + testHandler.On("List", testMock.Anything, "/", testMock.Anything).Return( + []response.Object{{IsDir: true, Name: "1"}, {IsDir: false, Name: "2"}}, + nil, + ) + fs.Handler = testHandler + res, err := fs.ListPhysical(ctx, "/") + asserts.NoError(err) + asserts.Len(res, 1) + asserts.Equal("1", res[0].Name) + } +} + func TestFileSystem_List(t *testing.T) { asserts := assert.New(t) fs := &FileSystem{User: &model.User{ diff --git a/pkg/filesystem/upload_test.go b/pkg/filesystem/upload_test.go index 04438c4..8010664 100644 --- a/pkg/filesystem/upload_test.go +++ b/pkg/filesystem/upload_test.go @@ -27,7 +27,8 @@ type FileHeaderMock struct { } func (m FileHeaderMock) List(ctx context.Context, path string, recursive bool) ([]response.Object, error) { - panic("implement me") + args := m.Called(ctx, path, recursive) + return args.Get(0).([]response.Object), args.Error(1) } func (m FileHeaderMock) Get(ctx context.Context, path string) (response.RSCloser, error) { diff --git a/routers/controllers/admin.go b/routers/controllers/admin.go index 202bece..3e347db 100644 --- a/routers/controllers/admin.go +++ b/routers/controllers/admin.go @@ -413,3 +413,14 @@ func AdminCreateImportTask(c *gin.Context) { c.JSON(200, ErrorResponse(err)) } } + +// AdminListFolders 列出用户或外部文件系统目录 +func AdminListFolders(c *gin.Context) { + var service admin.ListFolderService + if err := c.ShouldBindUri(&service); err == nil { + res := service.List(c) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index aeb5570..9e824cf 100644 --- a/routers/router.go +++ b/routers/router.go @@ -367,6 +367,9 @@ func InitMasterRouter() *gin.Engine { file.GET("preview/:id", controllers.AdminGetFile) // 删除 file.POST("delete", controllers.AdminDeleteFile) + // 列出用户或外部文件系统目录 + file.GET("folders/:type/:id/*path", + controllers.AdminListFolders) } share := admin.Group("share") diff --git a/service/admin/file.go b/service/admin/file.go index c1a8aa3..a08a3fa 100644 --- a/service/admin/file.go +++ b/service/admin/file.go @@ -22,6 +22,71 @@ type FileBatchService struct { Force bool `json:"force"` } +// ListFolderService 列目录结构 +type ListFolderService struct { + Path string `uri:"path" binding:"required,max=65535"` + ID uint `uri:"id" binding:"required"` + Type string `uri:"type" binding:"eq=policy|eq=user"` +} + +// List 列出指定路径下的目录 +func (service *ListFolderService) List(c *gin.Context) serializer.Response { + if service.Type == "policy" { + // 列取存储策略中的目录 + policy, err := model.GetPolicyByID(service.ID) + if err != nil { + return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err) + } + + // 创建文件系统 + fs, err := filesystem.NewAnonymousFileSystem() + if err != nil { + return serializer.Err(serializer.CodeInternalSetting, "无法创建文件系统", err) + } + defer fs.Recycle() + + // 列取存储策略中的文件 + fs.Policy = &policy + res, err := fs.ListPhysical(c.Request.Context(), service.Path) + if err != nil { + return serializer.Err(serializer.CodeIOFailed, "无法列取目录", err) + } + + return serializer.Response{ + Data: map[string]interface{}{ + "objects": res, + }, + } + + } + + // 列取用户空间目录 + // 查找用户 + user, err := model.GetUserByID(service.ID) + if err != nil { + return serializer.Err(serializer.CodeNotFound, "用户不存在", err) + } + + // 创建文件系统 + fs, err := filesystem.NewFileSystem(&user) + if err != nil { + return serializer.Err(serializer.CodeInternalSetting, "无法创建文件系统", err) + } + defer fs.Recycle() + + // 列取目录 + res, err := fs.List(c.Request.Context(), service.Path, nil) + if err != nil { + return serializer.Err(serializer.CodeIOFailed, "无法列取目录", err) + } + + return serializer.Response{ + Data: map[string]interface{}{ + "objects": res, + }, + } +} + // Delete 删除文件 func (service *FileBatchService) Delete(c *gin.Context) serializer.Response { files, err := model.GetFilesByIDs(service.ID, 0)