mirror of https://github.com/cloudreve/Cloudreve
Feat: WebDAV mount folders
parent
86889e2fac
commit
b4219927d6
|
@ -30,6 +30,13 @@ func (folder *Folder) Create() (uint, error) {
|
||||||
return folder.ID, nil
|
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的子目录,不存在则返回错误
|
// GetChild 返回folder下名为name的子目录,不存在则返回错误
|
||||||
func (folder *Folder) GetChild(name string) (*Folder, error) {
|
func (folder *Folder) GetChild(name string) (*Folder, error) {
|
||||||
var resFolder Folder
|
var resFolder Folder
|
||||||
|
@ -265,6 +272,11 @@ func (folder *Folder) Rename(new string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mount 目录挂载
|
||||||
|
func (folder *Folder) Mount(new uint) error {
|
||||||
|
return DB.Model(&folder).Update("policy_id", new).Error
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
实现 FileInfo.FileInfo 接口
|
实现 FileInfo.FileInfo 接口
|
||||||
TODO 测试
|
TODO 测试
|
||||||
|
|
|
@ -13,9 +13,11 @@ const (
|
||||||
FileID // 文件ID
|
FileID // 文件ID
|
||||||
FolderID // 目录ID
|
FolderID // 目录ID
|
||||||
TagID // 标签ID
|
TagID // 标签ID
|
||||||
|
PolicyID // 存储策略ID
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
// ErrTypeNotMatch ID类型不匹配
|
||||||
ErrTypeNotMatch = errors.New("ID类型不匹配")
|
ErrTypeNotMatch = errors.New("ID类型不匹配")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,8 @@ package serializer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/hashid"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type quota struct {
|
type quota struct {
|
||||||
|
@ -20,6 +22,58 @@ type storagePacks struct {
|
||||||
ExpirationDate string `json:"expiration_date"`
|
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 序列化用户存储配额概况响应
|
// BuildUserQuotaResponse 序列化用户存储配额概况响应
|
||||||
func BuildUserQuotaResponse(user *model.User, packs []model.StoragePack) Response {
|
func BuildUserQuotaResponse(user *model.User, packs []model.StoragePack) Response {
|
||||||
packSize := user.GetAvailablePackSize()
|
packSize := user.GetAvailablePackSize()
|
||||||
|
|
|
@ -135,3 +135,15 @@ func UserStorage(c *gin.Context) {
|
||||||
res := serializer.BuildUserStorageResponse(*currUser)
|
res := serializer.BuildUserStorageResponse(*currUser)
|
||||||
c.JSON(200, res)
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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账户
|
// CreateWebDAVAccounts 创建WebDAV账户
|
||||||
func CreateWebDAVAccounts(c *gin.Context) {
|
func CreateWebDAVAccounts(c *gin.Context) {
|
||||||
var service setting.WebDAVAccountCreateService
|
var service setting.WebDAVAccountCreateService
|
||||||
|
@ -74,3 +85,14 @@ func CreateWebDAVAccounts(c *gin.Context) {
|
||||||
c.JSON(200, ErrorResponse(err))
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -253,6 +253,13 @@ func InitMasterRouter() *gin.Engine {
|
||||||
authn.PUT("", controllers.StartRegAuthn)
|
authn.PUT("", controllers.StartRegAuthn)
|
||||||
authn.PUT("finish", controllers.FinishRegAuthn)
|
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.POST("accounts", controllers.CreateWebDAVAccounts)
|
||||||
// 删除账号
|
// 删除账号
|
||||||
webdav.DELETE("accounts/:id", controllers.DeleteWebDAVAccounts)
|
webdav.DELETE("accounts/:id", controllers.DeleteWebDAVAccounts)
|
||||||
|
// 删除目录挂载
|
||||||
|
webdav.DELETE("mount/:id",
|
||||||
|
middleware.HashID(hashid.FolderID),
|
||||||
|
controllers.DeleteWebDAVMounts,
|
||||||
|
)
|
||||||
|
// 创建目录挂载
|
||||||
|
webdav.POST("mount", controllers.CreateWebDAVMounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
model "github.com/HFO4/cloudreve/models"
|
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/serializer"
|
||||||
"github.com/HFO4/cloudreve/pkg/util"
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
@ -22,6 +24,65 @@ type WebDAVAccountCreateService struct {
|
||||||
Name string `json:"name" binding:"required,min=1,max=255"`
|
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账户
|
// Create 创建WebDAV账户
|
||||||
func (service *WebDAVAccountCreateService) Create(c *gin.Context, user *model.User) serializer.Response {
|
func (service *WebDAVAccountCreateService) Create(c *gin.Context, user *model.User) serializer.Response {
|
||||||
account := model.Webdav{
|
account := model.Webdav{
|
||||||
|
@ -53,7 +114,12 @@ func (service *WebDAVAccountService) Delete(c *gin.Context, user *model.User) se
|
||||||
// Accounts 列出WebDAV账号
|
// Accounts 列出WebDAV账号
|
||||||
func (service *WebDAVListService) Accounts(c *gin.Context, user *model.User) serializer.Response {
|
func (service *WebDAVListService) Accounts(c *gin.Context, user *model.User) serializer.Response {
|
||||||
accounts := model.ListWebDAVAccounts(user.ID)
|
accounts := model.ListWebDAVAccounts(user.ID)
|
||||||
|
|
||||||
|
// 查找挂载了存储策略的目录
|
||||||
|
folders := model.GetMountedFolders(user.ID)
|
||||||
|
|
||||||
return serializer.Response{Data: map[string]interface{}{
|
return serializer.Response{Data: map[string]interface{}{
|
||||||
"accounts": accounts,
|
"accounts": accounts,
|
||||||
|
"folders": serializer.BuildMountedFolderRes(folders, user.Group.PolicyList),
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue