mirror of
				https://github.com/cloudreve/cloudreve.git
				synced 2025-10-31 16:49:03 +08:00 
			
		
		
		
	Feat: import file from existing outer folder
This commit is contained in:
		
							
								
								
									
										2
									
								
								assets
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								assets
									
									
									
									
									
								
							 Submodule assets updated: 10e5497cfb...df766e4438
									
								
							| @ -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) | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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 := "" | ||||
|  | ||||
| @ -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{ | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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)) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -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") | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 HFO4
					HFO4