mirror of
https://github.com/halejohn/Cloudreve.git
synced 2026-01-26 09:34:57 +08:00
Feat: delete objects
This commit is contained in:
@@ -17,4 +17,5 @@ var (
|
||||
ErrPathNotExist = serializer.NewError(404, "路径不存在", nil)
|
||||
ErrObjectNotExist = serializer.NewError(404, "文件不存在", nil)
|
||||
ErrIO = serializer.NewError(serializer.CodeIOFailed, "无法读取文件数据", nil)
|
||||
ErrDBListObjects = serializer.NewError(serializer.CodeDBError, "无法列取对象记录", nil)
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (io.ReadSeeke
|
||||
if !exist {
|
||||
return nil, ErrObjectNotExist
|
||||
}
|
||||
fs.Target = &file
|
||||
fs.FileTarget = []model.File{file}
|
||||
|
||||
// 将当前存储策略重设为文件使用的
|
||||
fs.Policy = file.GetPolicy()
|
||||
@@ -102,3 +102,51 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (io.ReadSeeke
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// deleteGroupedFile 对分组好的文件执行删除操作,
|
||||
// 返回每个分组失败的文件列表
|
||||
func (fs *FileSystem) deleteGroupedFile(ctx context.Context, files map[uint][]*model.File) map[uint][]string {
|
||||
// 失败的文件列表
|
||||
failed := make(map[uint][]string, len(files))
|
||||
|
||||
for policyID, toBeDeletedFiles := range files {
|
||||
// 列举出需要物理删除的文件的物理路径
|
||||
sourceNames := make([]string, 0, len(toBeDeletedFiles))
|
||||
for i := 0; i < len(toBeDeletedFiles); i++ {
|
||||
sourceNames = append(sourceNames, toBeDeletedFiles[i].SourceName)
|
||||
}
|
||||
|
||||
// 切换上传策略
|
||||
fs.Policy = toBeDeletedFiles[0].GetPolicy()
|
||||
err := fs.dispatchHandler()
|
||||
if err != nil {
|
||||
failed[policyID] = sourceNames
|
||||
continue
|
||||
}
|
||||
|
||||
// 执行删除
|
||||
failedFile, _ := fs.Handler.Delete(ctx, sourceNames)
|
||||
failed[policyID] = failedFile
|
||||
|
||||
}
|
||||
|
||||
return failed
|
||||
}
|
||||
|
||||
// GroupFileByPolicy 将目标文件按照存储策略分组
|
||||
func (fs *FileSystem) GroupFileByPolicy(ctx context.Context, files []model.File) map[uint][]*model.File {
|
||||
var policyGroup = make(map[uint][]*model.File)
|
||||
|
||||
for key := range files {
|
||||
if file, ok := policyGroup[files[key].GetPolicy().ID]; ok {
|
||||
// 如果已存在分组,直接追加
|
||||
policyGroup[files[key].GetPolicy().ID] = append(file, &files[key])
|
||||
} else {
|
||||
// 分布不存在,创建
|
||||
policyGroup[files[key].GetPolicy().ID] = make([]*model.File, 0)
|
||||
policyGroup[files[key].GetPolicy().ID] = append(policyGroup[files[key].GetPolicy().ID], &files[key])
|
||||
}
|
||||
}
|
||||
|
||||
return policyGroup
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestFileSystem_GetContent(t *testing.T) {
|
||||
asserts.Equal(ErrObjectNotExist, err)
|
||||
asserts.Nil(rs)
|
||||
|
||||
// 未知上传策略
|
||||
// 未知存储策略
|
||||
file, err := os.Create("TestFileSystem_GetContent.txt")
|
||||
asserts.NoError(err)
|
||||
_ = file.Close()
|
||||
|
||||
@@ -33,10 +33,12 @@ type Handler interface {
|
||||
type FileSystem struct {
|
||||
// 文件系统所有者
|
||||
User *model.User
|
||||
// 操作文件使用的上传策略
|
||||
// 操作文件使用的存储策略
|
||||
Policy *model.Policy
|
||||
// 当前正在处理的文件对象
|
||||
Target *model.File
|
||||
FileTarget []model.File
|
||||
// 当前正在处理的目录对象
|
||||
DirTarget []model.Folder
|
||||
|
||||
/*
|
||||
钩子函数
|
||||
@@ -100,3 +102,23 @@ func NewFileSystemFromContext(c *gin.Context) (*FileSystem, error) {
|
||||
fs, err := NewFileSystem(user.(*model.User))
|
||||
return fs, err
|
||||
}
|
||||
|
||||
// SetTargetFile 设置当前处理的目标文件
|
||||
func (fs *FileSystem) SetTargetFile(files *[]model.File) {
|
||||
if len(fs.FileTarget) == 0 {
|
||||
fs.FileTarget = *files
|
||||
} else {
|
||||
fs.FileTarget = append(fs.FileTarget, *files...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// SetTargetDir 设置当前处理的目标目录
|
||||
func (fs *FileSystem) SetTargetDir(dirs *[]model.Folder) {
|
||||
if len(fs.DirTarget) == 0 {
|
||||
fs.DirTarget = *dirs
|
||||
} else {
|
||||
fs.DirTarget = append(fs.DirTarget, *dirs...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,20 +63,19 @@ func (handler Handler) Put(ctx context.Context, file io.ReadCloser, dst string,
|
||||
}
|
||||
|
||||
// Delete 删除一个或多个文件,
|
||||
// 返回已删除的文件,及遇到的最后一个错误
|
||||
// 返回未删除的文件,及遇到的最后一个错误
|
||||
func (handler Handler) Delete(ctx context.Context, files []string) ([]string, error) {
|
||||
deleted := make([]string, 0, len(files))
|
||||
deleteFailed := make([]string, 0, len(files))
|
||||
var retErr error
|
||||
|
||||
for _, value := range files {
|
||||
err := os.Remove(value)
|
||||
if err == nil {
|
||||
deleted = append(deleted, value)
|
||||
} else {
|
||||
if err != nil {
|
||||
util.Log().Warning("无法删除文件,%s", err)
|
||||
retErr = err
|
||||
deleteFailed = append(deleteFailed, value)
|
||||
}
|
||||
}
|
||||
|
||||
return deleted, retErr
|
||||
return deleteFailed, retErr
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"path"
|
||||
)
|
||||
|
||||
@@ -22,6 +25,113 @@ type Object struct {
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
// Delete 递归删除对象
|
||||
func (fs *FileSystem) Delete(ctx context.Context, dirs, files []string) error {
|
||||
// 已删除的总容量,map用于去重
|
||||
var deletedStorage = make(map[uint]uint64)
|
||||
// 已删除的文件ID
|
||||
var deletedFileIDs = make([]uint, 0, len(fs.FileTarget))
|
||||
// 删除失败的文件的父目录ID
|
||||
|
||||
// 所有文件的ID
|
||||
var allFileIDs = make([]uint, 0, len(fs.FileTarget))
|
||||
|
||||
// 列出要删除的目录
|
||||
if len(dirs) > 0 {
|
||||
err := fs.ListDeleteDirs(ctx, dirs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 列出要删除的文件
|
||||
if len(files) > 0 {
|
||||
err := fs.ListDeleteFiles(ctx, files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 去除待删除文件中包含软连接的部分
|
||||
filesToBeDelete, err := model.RemoveFilesWithSoftLinks(fs.FileTarget)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
|
||||
// 根据存储策略将文件分组
|
||||
policyGroup := fs.GroupFileByPolicy(ctx, filesToBeDelete)
|
||||
|
||||
// 按照存储策略分组删除对象
|
||||
failed := fs.deleteGroupedFile(ctx, policyGroup)
|
||||
|
||||
for i := 0; i < len(fs.FileTarget); i++ {
|
||||
if util.ContainsString(failed[fs.FileTarget[i].PolicyID], fs.FileTarget[i].SourceName) {
|
||||
// TODO 删除失败时不删除文件记录及父目录
|
||||
} else {
|
||||
deletedFileIDs = append(deletedFileIDs, fs.FileTarget[i].ID)
|
||||
deletedStorage[fs.FileTarget[i].ID] = fs.FileTarget[i].Size
|
||||
}
|
||||
allFileIDs = append(allFileIDs, fs.FileTarget[i].ID)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件记录
|
||||
err := model.DeleteFileByIDs(allFileIDs)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
|
||||
// 归还容量
|
||||
var total uint64
|
||||
for _, value := range deletedStorage {
|
||||
total += value
|
||||
}
|
||||
fs.User.IncreaseStorage(total)
|
||||
|
||||
// 删除目录
|
||||
var allFolderIDs = make([]uint, 0, len(fs.DirTarget))
|
||||
for _, value := range fs.DirTarget {
|
||||
allFolderIDs = append(allFolderIDs, value.ID)
|
||||
}
|
||||
err = model.DeleteFolderByIDs(allFolderIDs)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
|
||||
if notDeleted := len(fs.FileTarget) - len(deletedFileIDs); notDeleted > 0 {
|
||||
return serializer.NewError(serializer.CodeNotFullySuccess, fmt.Sprintf("有 %d 个文件未能成功删除,已删除它们的文件记录", notDeleted), nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListDeleteDirs 递归列出要删除目录,及目录下所有文件
|
||||
func (fs *FileSystem) ListDeleteDirs(ctx context.Context, dirs []string) error {
|
||||
// 列出所有递归子目录
|
||||
folders, err := model.GetRecursiveChildFolder(dirs, fs.User.ID)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
fs.SetTargetDir(&folders)
|
||||
|
||||
// 检索目录下的子文件
|
||||
files, err := model.GetChildFilesOfFolders(&folders)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
fs.SetTargetFile(&files)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListDeleteFiles 根据给定的路径列出要删除的文件
|
||||
func (fs *FileSystem) ListDeleteFiles(ctx context.Context, paths []string) error {
|
||||
files, err := model.GetFileByPaths(paths, fs.User.ID)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
fs.SetTargetFile(&files)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 列出路径下的内容,
|
||||
// pathProcessor为最终对象路径的处理钩子。
|
||||
// 有些情况下(如在分享页面列对象)时,
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
*/
|
||||
|
||||
// 文件/路径名保留字符
|
||||
var reservedCharacter = []string{"\\", "?", "*", "<", "\"", ":", ">", "/"}
|
||||
var reservedCharacter = []string{"\\", "?", "*", "<", "\"", ":", ">", "/", "|"}
|
||||
|
||||
// ValidateLegalName 验证文件名/文件夹名是否合法
|
||||
func (fs *FileSystem) ValidateLegalName(ctx context.Context, name string) bool {
|
||||
|
||||
Reference in New Issue
Block a user