From b4219927d6030517ef518fa381f8a597487dcd2d Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Tue, 18 Feb 2020 13:45:59 +0800 Subject: [PATCH] Feat: WebDAV mount folders --- models/folder.go | 12 +++++++ pkg/hashid/hash.go | 2 ++ pkg/serializer/vas.go | 54 ++++++++++++++++++++++++++++ routers/controllers/user.go | 12 +++++++ routers/controllers/webdav.go | 22 ++++++++++++ routers/router.go | 14 ++++++++ service/setting/webdav.go | 66 +++++++++++++++++++++++++++++++++++ service/user/setting.go | 27 ++++++++++++++ 8 files changed, 209 insertions(+) create mode 100644 service/user/setting.go diff --git a/models/folder.go b/models/folder.go index 46b9b55..06f1364 100644 --- a/models/folder.go +++ b/models/folder.go @@ -30,6 +30,13 @@ func (folder *Folder) Create() (uint, error) { return folder.ID, nil } +// GetMountedFolders 列出已挂载存储策略的目录 +func GetMountedFolders(uid uint) []Folder { + var folders []Folder + DB.Where("owner_id = ? and policy_id <> ?", uid, 0).Find(&folders) + return folders +} + // GetChild 返回folder下名为name的子目录,不存在则返回错误 func (folder *Folder) GetChild(name string) (*Folder, error) { var resFolder Folder @@ -265,6 +272,11 @@ func (folder *Folder) Rename(new string) error { return nil } +// Mount 目录挂载 +func (folder *Folder) Mount(new uint) error { + return DB.Model(&folder).Update("policy_id", new).Error +} + /* 实现 FileInfo.FileInfo 接口 TODO 测试 diff --git a/pkg/hashid/hash.go b/pkg/hashid/hash.go index 5becb06..ce89b48 100644 --- a/pkg/hashid/hash.go +++ b/pkg/hashid/hash.go @@ -13,9 +13,11 @@ const ( FileID // 文件ID FolderID // 目录ID TagID // 标签ID + PolicyID // 存储策略ID ) var ( + // ErrTypeNotMatch ID类型不匹配 ErrTypeNotMatch = errors.New("ID类型不匹配") ) diff --git a/pkg/serializer/vas.go b/pkg/serializer/vas.go index f38a0ec..582aa62 100644 --- a/pkg/serializer/vas.go +++ b/pkg/serializer/vas.go @@ -2,6 +2,8 @@ package serializer import ( model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/hashid" + "github.com/HFO4/cloudreve/pkg/util" ) type quota struct { @@ -20,6 +22,58 @@ type storagePacks struct { ExpirationDate string `json:"expiration_date"` } +// MountedFolders 已挂载的目录 +type MountedFolders struct { + ID string `json:"id"` + Name string `json:"name"` + PolicyName string `json:"policy_name"` +} + +type policyOptions struct { + Name string `json:"name"` + ID string `json:"id"` +} + +// BuildPolicySettingRes 构建存储策略选项选择 +func BuildPolicySettingRes(policies []model.Policy, current *model.Policy) Response { + options := make([]policyOptions, 0, len(policies)) + for _, policy := range policies { + options = append(options, policyOptions{ + Name: policy.Name, + ID: hashid.HashID(policy.ID, hashid.PolicyID), + }) + } + + return Response{ + Data: map[string]interface{}{ + "options": options, + "current": policyOptions{ + Name: current.Name, + ID: hashid.HashID(current.ID, hashid.PolicyID), + }, + }, + } +} + +// BuildMountedFolderRes 构建已挂载目录响应,list为当前用户可用存储策略ID +func BuildMountedFolderRes(folders []model.Folder, list []uint) []MountedFolders { + res := make([]MountedFolders, 0, len(folders)) + for _, folder := range folders { + single := MountedFolders{ + ID: hashid.HashID(folder.ID, hashid.FolderID), + Name: folder.Name, + PolicyName: "[已失效存储策略]", + } + if policy, err := model.GetPolicyByID(folder.PolicyID); err == nil && util.ContainsUint(list, policy.ID) { + single.PolicyName = policy.Name + } + + res = append(res, single) + } + + return res +} + // BuildUserQuotaResponse 序列化用户存储配额概况响应 func BuildUserQuotaResponse(user *model.User, packs []model.StoragePack) Response { packSize := user.GetAvailablePackSize() diff --git a/routers/controllers/user.go b/routers/controllers/user.go index 0e75ffe..7b426ee 100644 --- a/routers/controllers/user.go +++ b/routers/controllers/user.go @@ -135,3 +135,15 @@ func UserStorage(c *gin.Context) { res := serializer.BuildUserStorageResponse(*currUser) c.JSON(200, res) } + +// UserAvailablePolicies 用户存储策略设置 +func UserAvailablePolicies(c *gin.Context) { + var service user.SettingService + if err := c.ShouldBindUri(&service); err == nil { + res := service.Policy(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } + +} diff --git a/routers/controllers/webdav.go b/routers/controllers/webdav.go index 48e91c3..51a058b 100644 --- a/routers/controllers/webdav.go +++ b/routers/controllers/webdav.go @@ -64,6 +64,17 @@ func DeleteWebDAVAccounts(c *gin.Context) { } } +// DeleteWebDAVMounts 删除WebDAV挂载 +func DeleteWebDAVMounts(c *gin.Context) { + var service setting.WebDAVListService + if err := c.ShouldBindUri(&service); err == nil { + res := service.Unmount(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} + // CreateWebDAVAccounts 创建WebDAV账户 func CreateWebDAVAccounts(c *gin.Context) { var service setting.WebDAVAccountCreateService @@ -74,3 +85,14 @@ func CreateWebDAVAccounts(c *gin.Context) { c.JSON(200, ErrorResponse(err)) } } + +// CreateWebDAVMounts 创建WebDAV目录挂载 +func CreateWebDAVMounts(c *gin.Context) { + var service setting.WebDAVMountCreateService + if err := c.ShouldBindJSON(&service); err == nil { + res := service.Create(c, CurrentUser(c)) + c.JSON(200, res) + } else { + c.JSON(200, ErrorResponse(err)) + } +} diff --git a/routers/router.go b/routers/router.go index eb61fd0..0af89fd 100644 --- a/routers/router.go +++ b/routers/router.go @@ -253,6 +253,13 @@ func InitMasterRouter() *gin.Engine { authn.PUT("", controllers.StartRegAuthn) authn.PUT("finish", controllers.FinishRegAuthn) } + + // 用户设置 + setting := user.Group("setting") + { + // 获取用户可选存储策略 + setting.GET("policies", controllers.UserAvailablePolicies) + } } // 文件 @@ -390,6 +397,13 @@ func InitMasterRouter() *gin.Engine { webdav.POST("accounts", controllers.CreateWebDAVAccounts) // 删除账号 webdav.DELETE("accounts/:id", controllers.DeleteWebDAVAccounts) + // 删除目录挂载 + webdav.DELETE("mount/:id", + middleware.HashID(hashid.FolderID), + controllers.DeleteWebDAVMounts, + ) + // 创建目录挂载 + webdav.POST("mount", controllers.CreateWebDAVMounts) } } diff --git a/service/setting/webdav.go b/service/setting/webdav.go index 9ae5a06..884b57e 100644 --- a/service/setting/webdav.go +++ b/service/setting/webdav.go @@ -2,6 +2,8 @@ package setting import ( model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/filesystem" + "github.com/HFO4/cloudreve/pkg/hashid" "github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/util" "github.com/gin-gonic/gin" @@ -22,6 +24,65 @@ type WebDAVAccountCreateService struct { Name string `json:"name" binding:"required,min=1,max=255"` } +// WebDAVMountCreateService WebDAV 挂载创建服务 +type WebDAVMountCreateService struct { + Path string `json:"path" binding:"required,min=1,max=65535"` + Policy string `json:"policy" binding:"required,min=1"` +} + +// Create 创建目录挂载 +func (service *WebDAVMountCreateService) Create(c *gin.Context, user *model.User) serializer.Response { + // 创建文件系统 + fs, err := filesystem.NewFileSystem(user) + if err != nil { + return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) + } + defer fs.Recycle() + + // 检索要挂载的目录 + exist, folder := fs.IsPathExist(service.Path) + if !exist { + return serializer.Err(serializer.CodeNotFound, "路径不存在", err) + } + + // 检索要挂载的存储策略 + policyID, err := hashid.DecodeHashID(service.Policy, hashid.PolicyID) + if err != nil { + return serializer.Err(serializer.CodeNotFound, "存储策略不存在", err) + } + + // 检查存储策略是否可用 + if policy, err := model.GetPolicyByID(policyID); err != nil || !util.ContainsUint(user.Group.PolicyList, policy.ID) { + return serializer.Err(serializer.CodeNotFound, "存储策略不可用", err) + } + + // 挂载 + if err := folder.Mount(policyID); err != nil { + return serializer.Err(serializer.CodeDBError, "挂载失败", err) + } + + return serializer.Response{ + Data: map[string]interface{}{ + "id": hashid.HashID(folder.ID, hashid.FolderID), + }, + } +} + +// Unmount 取消目录挂载 +func (service *WebDAVListService) Unmount(c *gin.Context, user *model.User) serializer.Response { + folderID, _ := c.Get("object_id") + folder, err := model.GetFoldersByIDs([]uint{folderID.(uint)}, user.ID) + if err != nil || len(folder) == 0 { + return serializer.Err(serializer.CodeNotFound, "目录不存在", err) + } + + if err := folder[0].Mount(0); err != nil { + return serializer.Err(serializer.CodeDBError, "取消挂载失败", err) + } + + return serializer.Response{} +} + // Create 创建WebDAV账户 func (service *WebDAVAccountCreateService) Create(c *gin.Context, user *model.User) serializer.Response { account := model.Webdav{ @@ -53,7 +114,12 @@ func (service *WebDAVAccountService) Delete(c *gin.Context, user *model.User) se // Accounts 列出WebDAV账号 func (service *WebDAVListService) Accounts(c *gin.Context, user *model.User) serializer.Response { accounts := model.ListWebDAVAccounts(user.ID) + + // 查找挂载了存储策略的目录 + folders := model.GetMountedFolders(user.ID) + return serializer.Response{Data: map[string]interface{}{ "accounts": accounts, + "folders": serializer.BuildMountedFolderRes(folders, user.Group.PolicyList), }} } diff --git a/service/user/setting.go b/service/user/setting.go new file mode 100644 index 0000000..a2c4afa --- /dev/null +++ b/service/user/setting.go @@ -0,0 +1,27 @@ +package user + +import ( + model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/serializer" + "github.com/gin-gonic/gin" +) + +// SettingService 通用设置服务 +type SettingService struct { +} + +// Policy 获取用户存储策略设置 +func (service *SettingService) Policy(c *gin.Context, user *model.User) serializer.Response { + // 取得用户可用存储策略 + available := make([]model.Policy, 0, len(user.Group.PolicyList)) + for _, id := range user.Group.PolicyList { + if policy, err := model.GetPolicyByID(id); err == nil { + available = append(available, policy) + } + } + + // 取得用户当前策略 + current := user.Policy + + return serializer.BuildPolicySettingRes(available, ¤t) +}