mirror of
https://github.com/halejohn/Cloudreve.git
synced 2026-01-26 09:34:57 +08:00
Modify: use pure tree structure in file system scheme
This commit is contained in:
336
pkg/filesystem/manage.go
Normal file
336
pkg/filesystem/manage.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"path"
|
||||
)
|
||||
|
||||
/* =================
|
||||
文件/目录管理
|
||||
=================
|
||||
*/
|
||||
|
||||
// Object 文件或者目录
|
||||
type Object struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Pic string `json:"pic"`
|
||||
Size uint64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
// Rename 重命名对象
|
||||
func (fs *FileSystem) Rename(ctx context.Context, src, new string) (err error) {
|
||||
// 验证新名字
|
||||
if !fs.ValidateLegalName(ctx, new) || !fs.ValidateExtension(ctx, new) {
|
||||
return ErrIllegalObjectName
|
||||
}
|
||||
|
||||
// 如果源对象是文件
|
||||
fileExist, file := fs.IsFileExist(src)
|
||||
if fileExist {
|
||||
err = file.Rename(new)
|
||||
return err
|
||||
}
|
||||
|
||||
// 源对象是目录
|
||||
folderExist, folder := fs.IsPathExist(src)
|
||||
if folderExist {
|
||||
err = folder.Rename(new)
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// Copy 复制src目录下的文件或目录到dst,
|
||||
// 暂时只支持单文件
|
||||
func (fs *FileSystem) Copy(ctx context.Context, dirs, files []uint, src, dst string) error {
|
||||
// 获取目的目录
|
||||
isDstExist, dstFolder := fs.IsPathExist(dst)
|
||||
isSrcExist, srcFolder := fs.IsPathExist(src)
|
||||
// 不存在时返回空的结果
|
||||
if !isDstExist || !isSrcExist {
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// 记录复制的文件的总容量
|
||||
var newUsedStorage uint64
|
||||
|
||||
// 复制目录
|
||||
if len(dirs) > 0 {
|
||||
subFileSizes, err := srcFolder.CopyFolderTo(dirs[0], dstFolder)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
newUsedStorage += subFileSizes
|
||||
}
|
||||
|
||||
// 复制文件
|
||||
if len(files) > 0 {
|
||||
subFileSizes, err := srcFolder.MoveOrCopyFileTo(files, dstFolder, true)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
newUsedStorage += subFileSizes
|
||||
}
|
||||
|
||||
// 扣除容量
|
||||
fs.User.IncreaseStorageWithoutCheck(newUsedStorage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move 移动文件和目录
|
||||
func (fs *FileSystem) Move(ctx context.Context, dirs, files []uint, src, dst string) error {
|
||||
// 获取目的目录
|
||||
isDstExist, dstFolder := fs.IsPathExist(dst)
|
||||
isSrcExist, srcFolder := fs.IsPathExist(src)
|
||||
// 不存在时返回空的结果
|
||||
if !isDstExist || !isSrcExist {
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// 处理目录及子文件移动
|
||||
err := srcFolder.MoveFolderTo(dirs, dstFolder)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
|
||||
// 处理文件移动
|
||||
_, err = srcFolder.MoveOrCopyFileTo(files, dstFolder, false)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
|
||||
// 移动文件
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete 递归删除对象
|
||||
func (fs *FileSystem) Delete(ctx context.Context, dirs, files []uint) 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 ErrDBDeleteObjects.WithError(err)
|
||||
}
|
||||
|
||||
// 归还容量
|
||||
var total uint64
|
||||
for _, value := range deletedStorage {
|
||||
total += value
|
||||
}
|
||||
fs.User.DeductionStorage(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 ErrDBDeleteObjects.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, ids []uint) error {
|
||||
// 列出所有递归子目录
|
||||
folders, err := model.GetRecursiveChildFolder(ids, fs.User.ID, true)
|
||||
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, ids []uint) error {
|
||||
files, err := model.GetFilesByIDs(ids, fs.User.ID)
|
||||
if err != nil {
|
||||
return ErrDBListObjects.WithError(err)
|
||||
}
|
||||
fs.SetTargetFile(&files)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 列出路径下的内容,
|
||||
// pathProcessor为最终对象路径的处理钩子。
|
||||
// 有些情况下(如在分享页面列对象)时,
|
||||
// 路径需要截取掉被分享目录路径之前的部分。
|
||||
func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor func(string) string) ([]Object, error) {
|
||||
// 获取父目录
|
||||
isExist, folder := fs.IsPathExist(dirPath)
|
||||
// 不存在时返回空的结果
|
||||
if !isExist {
|
||||
return []Object{}, nil
|
||||
}
|
||||
|
||||
var parentPath = path.Join(folder.PositionTemp, folder.Name)
|
||||
var childFolders []model.Folder
|
||||
var childFiles []model.File
|
||||
|
||||
// 获取子目录
|
||||
childFolders, _ = folder.GetChildFolder()
|
||||
|
||||
// 获取子文件
|
||||
childFiles, _ = folder.GetChildFiles()
|
||||
|
||||
// 汇总处理结果
|
||||
objects := make([]Object, 0, len(childFiles)+len(childFolders))
|
||||
// 所有对象的父目录
|
||||
var processedPath string
|
||||
|
||||
for _, subFolder := range childFolders {
|
||||
// 路径处理钩子,
|
||||
// 所有对象父目录都是一样的,所以只处理一次
|
||||
if processedPath == "" {
|
||||
if pathProcessor != nil {
|
||||
processedPath = pathProcessor(parentPath)
|
||||
} else {
|
||||
processedPath = parentPath
|
||||
}
|
||||
}
|
||||
|
||||
objects = append(objects, Object{
|
||||
ID: subFolder.ID,
|
||||
Name: subFolder.Name,
|
||||
Path: processedPath,
|
||||
Pic: "",
|
||||
Size: 0,
|
||||
Type: "dir",
|
||||
Date: subFolder.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
for _, file := range childFiles {
|
||||
if processedPath == "" {
|
||||
if pathProcessor != nil {
|
||||
processedPath = pathProcessor(parentPath)
|
||||
} else {
|
||||
processedPath = parentPath
|
||||
}
|
||||
}
|
||||
|
||||
objects = append(objects, Object{
|
||||
ID: file.ID,
|
||||
Name: file.Name,
|
||||
Path: processedPath,
|
||||
Pic: file.PicInfo,
|
||||
Size: file.Size,
|
||||
Type: "file",
|
||||
Date: file.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// CreateDirectory 根据给定的完整创建目录,不会递归创建
|
||||
func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) error {
|
||||
// 获取要创建目录的父路径和目录名
|
||||
fullPath = path.Clean(fullPath)
|
||||
base := path.Dir(fullPath)
|
||||
dir := path.Base(fullPath)
|
||||
|
||||
// 检查目录名是否合法
|
||||
if !fs.ValidateLegalName(ctx, dir) {
|
||||
return ErrIllegalObjectName
|
||||
}
|
||||
|
||||
// 父目录是否存在
|
||||
isExist, parent := fs.IsPathExist(base)
|
||||
if !isExist {
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// 是否有同名文件
|
||||
if ok, _ := fs.IsChildFileExist(parent, dir); ok {
|
||||
return ErrFileExisted
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
newFolder := model.Folder{
|
||||
Name: dir,
|
||||
ParentID: parent.ID,
|
||||
OwnerID: fs.User.ID,
|
||||
}
|
||||
_, err := newFolder.Create()
|
||||
|
||||
if err != nil {
|
||||
return ErrFolderExisted
|
||||
}
|
||||
return nil
|
||||
}
|
||||
453
pkg/filesystem/manage_test.go
Normal file
453
pkg/filesystem/manage_test.go
Normal file
@@ -0,0 +1,453 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/conf"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileSystem_List(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 成功,子目录包含文件和路径,不使用路径处理钩子
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(1, "/", 1))
|
||||
// folder
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "folder").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(5, "folder", 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(6, "sub_folder1").AddRow(7, "sub_folder2"))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(6, "sub_file1.txt").AddRow(7, "sub_file2.txt"))
|
||||
objects, err := fs.List(ctx, "/folder", nil)
|
||||
asserts.Len(objects, 4)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 成功,子目录包含文件和路径,使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(1, "/", 1))
|
||||
// folder
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "folder").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(2, "folder", 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "position"}).AddRow(6, "sub_folder1", "/folder").AddRow(7, "sub_folder2", "/folder"))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "dir"}).AddRow(6, "sub_file1.txt", "/folder").AddRow(7, "sub_file2.txt", "/folder"))
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 4)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
for _, value := range objects {
|
||||
asserts.Contains(value.Path, "prefix/")
|
||||
}
|
||||
|
||||
// 成功,子目录包含路径,使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(1, "/", 1))
|
||||
// folder
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "folder").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(2, "folder", 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "position"}))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "dir"}).AddRow(6, "sub_file1.txt", "/folder").AddRow(7, "sub_file2.txt", "/folder"))
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 2)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
for _, value := range objects {
|
||||
asserts.Contains(value.Path, "prefix/")
|
||||
}
|
||||
|
||||
// 成功,子目录下为空,使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(1, "/", 1))
|
||||
// folder
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "folder").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(2, "folder", 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "position"}))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "dir"}))
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 0)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 成功,子目录路径不存在
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}).AddRow(1, "/", 1))
|
||||
// folder
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "folder").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "name", "owner_id"}))
|
||||
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 0)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestFileSystem_CreateDirectory(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 目录名非法
|
||||
err := fs.CreateDirectory(ctx, "/ad/a+?")
|
||||
asserts.Equal(ErrIllegalObjectName, err)
|
||||
|
||||
// 父目录不存在
|
||||
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.Equal(ErrPathNotExist, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 存在同名文件
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(1, 1))
|
||||
// ad
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "ad").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(2, 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)files").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.Equal(ErrFileExisted, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 存在同名目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(1, 1))
|
||||
// ad
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "ad").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(2, 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)files").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT(.+)").WillReturnError(errors.New("s"))
|
||||
mock.ExpectRollback()
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.Error(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 成功创建
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(1, 1))
|
||||
// ad
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "ad").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(2, 1))
|
||||
|
||||
mock.ExpectQuery("SELECT(.+)files").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestFileSystem_ListDeleteFiles(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
|
||||
// 成功
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "1.txt").AddRow(2, "2.txt"))
|
||||
err := fs.ListDeleteFiles(context.Background(), []uint{1})
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 失败
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnError(errors.New("error"))
|
||||
err := fs.ListDeleteFiles(context.Background(), []uint{1})
|
||||
asserts.Error(err)
|
||||
asserts.Equal(serializer.CodeDBError, err.(serializer.AppError).Code)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_ListDeleteDirs(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
|
||||
// 成功
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id", "name"}).
|
||||
AddRow(4, "1.txt").
|
||||
AddRow(5, "2.txt").
|
||||
AddRow(6, "3.txt"),
|
||||
)
|
||||
err := fs.ListDeleteDirs(context.Background(), []uint{1})
|
||||
asserts.NoError(err)
|
||||
asserts.Len(fs.FileTarget, 3)
|
||||
asserts.Len(fs.DirTarget, 3)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 检索文件发生错误
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnError(errors.New("error"))
|
||||
err := fs.ListDeleteDirs(context.Background(), []uint{1})
|
||||
asserts.Error(err)
|
||||
asserts.Len(fs.DirTarget, 6)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
// 检索目录发生错误
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnError(errors.New("error"))
|
||||
err := fs.ListDeleteDirs(context.Background(), []uint{1})
|
||||
asserts.Error(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_Delete(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Storage: 3,
|
||||
Group: model.Group{MaxStorage: 3},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 全部未成功
|
||||
{
|
||||
// 列出要删除的目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).
|
||||
AddRow(4, "1.txt", "1.txt", 2, 1),
|
||||
)
|
||||
// 查询顶级的文件
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).AddRow(1, "1.txt", "1.txt", 1, 2))
|
||||
mock.ExpectQuery("SELECT(.+)files(.+)").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "policy_id", "source_name"}))
|
||||
// 查询上传策略
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
// 删除文件记录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)files").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 归还容量
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)users").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 删除目录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)folders").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
|
||||
err := fs.Delete(ctx, []uint{1}, []uint{1})
|
||||
asserts.Error(err)
|
||||
asserts.Equal(203, err.(serializer.AppError).Code)
|
||||
asserts.Equal(uint64(0), fs.User.Storage)
|
||||
}
|
||||
// 全部成功
|
||||
{
|
||||
file, err := os.Create("1.txt")
|
||||
file2, err := os.Create("2.txt")
|
||||
file.Close()
|
||||
file2.Close()
|
||||
asserts.NoError(err)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).
|
||||
AddRow(4, "1.txt", "1.txt", 2, 1),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).AddRow(1, "2.txt", "2.txt", 1, 2))
|
||||
mock.ExpectQuery("SELECT(.+)files(.+)").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "policy_id", "source_name"}))
|
||||
// 查询上传策略
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
// 删除文件记录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 归还容量
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 删除目录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
|
||||
fs.FileTarget = []model.File{}
|
||||
fs.DirTarget = []model.Folder{}
|
||||
err = fs.Delete(ctx, []uint{1}, []uint{1})
|
||||
asserts.NoError(err)
|
||||
asserts.Equal(uint64(0), fs.User.Storage)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFileSystem_Copy(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Storage: 3,
|
||||
Group: model.Group{MaxStorage: 3},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 目录不存在
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/dst").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/src").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
err := fs.Copy(ctx, []string{}, []string{}, "/src", "/dst")
|
||||
asserts.Equal(ErrPathNotExist, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 复制目录出错
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/src").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/dst").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
err := fs.Copy(ctx, []string{"test"}, []string{}, "/src", "/dst")
|
||||
asserts.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFileSystem_Move(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Storage: 3,
|
||||
Group: model.Group{MaxStorage: 3},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 目录不存在
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
err := fs.Move(ctx, []string{}, []string{}, "/src", "/dst")
|
||||
asserts.Equal(ErrPathNotExist, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 移动目录出错
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/src").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/dst").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
err := fs.Move(ctx, []string{"test"}, []string{}, "/src", "/dst")
|
||||
asserts.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/* =================
|
||||
@@ -15,335 +11,34 @@ import (
|
||||
=================
|
||||
*/
|
||||
|
||||
// Object 文件或者目录
|
||||
type Object struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Pic string `json:"pic"`
|
||||
Size uint64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
Date string `json:"date"`
|
||||
}
|
||||
|
||||
// Rename 重命名对象
|
||||
func (fs *FileSystem) Rename(ctx context.Context, src, new string) (err error) {
|
||||
// 验证新名字
|
||||
if !fs.ValidateLegalName(ctx, new) || !fs.ValidateExtension(ctx, new) {
|
||||
return ErrIllegalObjectName
|
||||
}
|
||||
|
||||
// 如果源对象是文件
|
||||
fileExist, file := fs.IsFileExist(src)
|
||||
if fileExist {
|
||||
err = file.Rename(new)
|
||||
return err
|
||||
}
|
||||
|
||||
// 源对象是目录
|
||||
folderExist, folder := fs.IsPathExist(src)
|
||||
if folderExist {
|
||||
err = folder.Rename(new)
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// Copy 复制src目录下的文件或目录到dst
|
||||
func (fs *FileSystem) Copy(ctx context.Context, dirs, files []string, src, dst string) error {
|
||||
// 获取目的目录
|
||||
isDstExist, dstFolder := fs.IsPathExist(dst)
|
||||
isSrcExist, srcFolder := fs.IsPathExist(src)
|
||||
// 不存在时返回空的结果
|
||||
if !isDstExist || !isSrcExist {
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// 记录复制的文件的总容量
|
||||
var newUsedStorage uint64
|
||||
|
||||
// 复制目录
|
||||
if len(dirs) > 0 {
|
||||
subFileSizes, err := srcFolder.MoveOrCopyFolderTo(dirs, dstFolder, true)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
newUsedStorage += subFileSizes
|
||||
}
|
||||
|
||||
// 复制文件
|
||||
if len(files) > 0 {
|
||||
subFileSizes, err := srcFolder.MoveOrCopyFileTo(files, dstFolder, true)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
newUsedStorage += subFileSizes
|
||||
}
|
||||
|
||||
// 扣除容量
|
||||
fs.User.IncreaseStorageWithoutCheck(newUsedStorage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Move 移动文件和目录
|
||||
func (fs *FileSystem) Move(ctx context.Context, dirs, files []string, src, dst string) error {
|
||||
// 获取目的目录
|
||||
isDstExist, dstFolder := fs.IsPathExist(dst)
|
||||
isSrcExist, srcFolder := fs.IsPathExist(src)
|
||||
// 不存在时返回空的结果
|
||||
if !isDstExist || !isSrcExist {
|
||||
return ErrPathNotExist
|
||||
}
|
||||
|
||||
// 处理目录及子文件移动
|
||||
_, err := srcFolder.MoveOrCopyFolderTo(dirs, dstFolder, false)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
|
||||
// 处理文件移动
|
||||
_, err = srcFolder.MoveOrCopyFileTo(files, dstFolder, false)
|
||||
if err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "操作失败,可能有重名冲突", err)
|
||||
}
|
||||
|
||||
// 移动文件
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 ErrDBDeleteObjects.WithError(err)
|
||||
}
|
||||
|
||||
// 归还容量
|
||||
var total uint64
|
||||
for _, value := range deletedStorage {
|
||||
total += value
|
||||
}
|
||||
fs.User.DeductionStorage(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 ErrDBDeleteObjects.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, true)
|
||||
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为最终对象路径的处理钩子。
|
||||
// 有些情况下(如在分享页面列对象)时,
|
||||
// 路径需要截取掉被分享目录路径之前的部分。
|
||||
func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor func(string) string) ([]Object, error) {
|
||||
// 获取父目录
|
||||
isExist, folder := fs.IsPathExist(dirPath)
|
||||
// 不存在时返回空的结果
|
||||
if !isExist {
|
||||
return []Object{}, nil
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var childFolders []model.Folder
|
||||
var childFiles []model.File
|
||||
wg.Add(2)
|
||||
|
||||
// 获取子目录
|
||||
go func() {
|
||||
childFolders, _ = folder.GetChildFolder()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// 获取子文件
|
||||
go func() {
|
||||
childFiles, _ = folder.GetChildFile()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// 汇总处理结果
|
||||
wg.Wait()
|
||||
objects := make([]Object, 0, len(childFiles)+len(childFolders))
|
||||
// 所有对象的父目录
|
||||
var processedPath string
|
||||
|
||||
for _, folder := range childFolders {
|
||||
// 路径处理钩子,
|
||||
// 所有对象父目录都是一样的,所以只处理一次
|
||||
if processedPath == "" {
|
||||
if pathProcessor != nil {
|
||||
processedPath = pathProcessor(folder.Position)
|
||||
} else {
|
||||
processedPath = folder.Position
|
||||
}
|
||||
}
|
||||
|
||||
objects = append(objects, Object{
|
||||
ID: folder.ID,
|
||||
Name: folder.Name,
|
||||
Path: processedPath,
|
||||
Pic: "",
|
||||
Size: 0,
|
||||
Type: "dir",
|
||||
Date: folder.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
for _, file := range childFiles {
|
||||
if processedPath == "" {
|
||||
if pathProcessor != nil {
|
||||
processedPath = pathProcessor(file.Dir)
|
||||
} else {
|
||||
processedPath = file.Dir
|
||||
}
|
||||
}
|
||||
|
||||
objects = append(objects, Object{
|
||||
ID: file.ID,
|
||||
Name: file.Name,
|
||||
Path: processedPath,
|
||||
Pic: file.PicInfo,
|
||||
Size: file.Size,
|
||||
Type: "file",
|
||||
Date: file.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// CreateDirectory 根据给定的完整创建目录,不会递归创建
|
||||
func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) error {
|
||||
// 获取要创建目录的父路径和目录名
|
||||
fullPath = path.Clean(fullPath)
|
||||
base := path.Dir(fullPath)
|
||||
dir := path.Base(fullPath)
|
||||
// 检查目录名是否合法
|
||||
if !fs.ValidateLegalName(ctx, dir) {
|
||||
return ErrIllegalObjectName
|
||||
}
|
||||
// 父目录是否存在
|
||||
isExist, parent := fs.IsPathExist(base)
|
||||
if !isExist {
|
||||
return ErrPathNotExist
|
||||
}
|
||||
// 是否有同名目录
|
||||
// TODO: 有了unique约束后可以不用在此检查
|
||||
b, _ := fs.IsPathExist(fullPath)
|
||||
if b {
|
||||
return ErrFolderExisted
|
||||
}
|
||||
// 是否有同名文件
|
||||
if ok, _ := fs.IsFileExist(path.Join(base, dir)); ok {
|
||||
return ErrFileExisted
|
||||
}
|
||||
// 创建目录
|
||||
newFolder := model.Folder{
|
||||
Name: dir,
|
||||
ParentID: parent.ID,
|
||||
Position: base,
|
||||
PositionAbsolute: fullPath,
|
||||
OwnerID: fs.User.ID,
|
||||
}
|
||||
_, err := newFolder.Create()
|
||||
return err
|
||||
}
|
||||
|
||||
// IsPathExist 返回给定目录是否存在
|
||||
// 如果存在就返回目录
|
||||
func (fs *FileSystem) IsPathExist(path string) (bool, *model.Folder) {
|
||||
folder, err := model.GetFolderByPath(path, fs.User.ID)
|
||||
return err == nil, &folder
|
||||
pathList := util.SplitPath(path)
|
||||
if len(pathList) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// 递归步入目录
|
||||
var currentFolder *model.Folder
|
||||
for _, folderName := range pathList {
|
||||
var err error
|
||||
|
||||
// 根目录
|
||||
if folderName == "/" {
|
||||
currentFolder, err = fs.User.Root()
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
currentFolder, err = currentFolder.GetChild(folderName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, currentFolder
|
||||
}
|
||||
|
||||
// IsFileExist 返回给定路径的文件是否存在
|
||||
@@ -351,7 +46,19 @@ func (fs *FileSystem) IsFileExist(fullPath string) (bool, *model.File) {
|
||||
basePath := path.Dir(fullPath)
|
||||
fileName := path.Base(fullPath)
|
||||
|
||||
file, err := model.GetFileByPathAndName(basePath, fileName, fs.User.ID)
|
||||
// 获得父目录
|
||||
exist, parent := fs.IsPathExist(basePath)
|
||||
if !exist {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return err == nil, &file
|
||||
file, err := parent.GetChildFile(fileName)
|
||||
|
||||
return err == nil, file
|
||||
}
|
||||
|
||||
// IsChildFileExist 确定folder目录下是否有名为name的文件
|
||||
func (fs *FileSystem) IsChildFileExist(folder *model.Folder, name string) (bool, *model.File) {
|
||||
file, err := folder.GetChildFile(name)
|
||||
return err == nil, file
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/conf"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -22,20 +17,43 @@ func TestFileSystem_IsFileExist(t *testing.T) {
|
||||
}}
|
||||
|
||||
// 存在
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/s", "1.txt").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"Name"}).AddRow("s"),
|
||||
)
|
||||
testResult, _ := fs.IsFileExist("/s/1.txt")
|
||||
asserts.True(testResult)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
{
|
||||
path := "/1.txt"
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(1, "1.txt").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "1.txt"))
|
||||
exist, file := fs.IsFileExist(path)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.True(exist)
|
||||
asserts.Equal(uint(1), file.ID)
|
||||
}
|
||||
|
||||
// 不存在
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/ss/dfsd", "1.txt").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"Name"}),
|
||||
)
|
||||
testResult, _ = fs.IsFileExist("/ss/dfsd/1.txt")
|
||||
asserts.False(testResult)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
// 文件不存在
|
||||
{
|
||||
path := "/1.txt"
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(1, "1.txt").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
exist, _ := fs.IsFileExist(path)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.False(exist)
|
||||
}
|
||||
|
||||
// 父目录不存在
|
||||
{
|
||||
path := "/1.txt"
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}))
|
||||
exist, _ := fs.IsFileExist(path)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.False(exist)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_IsPathExist(t *testing.T) {
|
||||
@@ -46,398 +64,67 @@ func TestFileSystem_IsPathExist(t *testing.T) {
|
||||
},
|
||||
}}
|
||||
|
||||
// 存在
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/s/sda").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("sda"),
|
||||
)
|
||||
testResult, folder := fs.IsPathExist("/s/sda")
|
||||
asserts.True(testResult)
|
||||
asserts.Equal("sda", folder.Name)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 不存在
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/s/sda").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
testResult, _ = fs.IsPathExist("/s/sda")
|
||||
asserts.False(testResult)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestFileSystem_List(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 成功,子目录包含文件和路径,不使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(5, "folder"))
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(6, "sub_folder1").AddRow(7, "sub_folder2"))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(6, "sub_file1.txt").AddRow(7, "sub_file2.txt"))
|
||||
objects, err := fs.List(ctx, "/folder", nil)
|
||||
asserts.Len(objects, 4)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 成功,子目录包含文件和路径,使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(5, "folder"))
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "position"}).AddRow(6, "sub_folder1", "/folder").AddRow(7, "sub_folder2", "/folder"))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "dir"}).AddRow(6, "sub_file1.txt", "/folder").AddRow(7, "sub_file2.txt", "/folder"))
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 4)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
for _, value := range objects {
|
||||
asserts.Contains(value.Path, "prefix/")
|
||||
}
|
||||
|
||||
// 成功,子目录包含路径,使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(5, "folder"))
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "position"}))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "dir"}).AddRow(6, "sub_file1.txt", "/folder").AddRow(7, "sub_file2.txt", "/folder"))
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 2)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
for _, value := range objects {
|
||||
asserts.Contains(value.Path, "prefix/")
|
||||
}
|
||||
|
||||
// 成功,子目录下为空,使用路径处理钩子
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(5, "folder"))
|
||||
mock.ExpectQuery("SELECT(.+)folder(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "position"}))
|
||||
mock.ExpectQuery("SELECT(.+)file(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "dir"}))
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 0)
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 成功,子目录路径不存在
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
|
||||
objects, err = fs.List(ctx, "/folder", func(s string) string {
|
||||
return "prefix" + s
|
||||
})
|
||||
asserts.Len(objects, 0)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestFileSystem_CreateDirectory(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 目录名非法
|
||||
err := fs.CreateDirectory(ctx, "/ad/a+?")
|
||||
asserts.Equal(ErrIllegalObjectName, err)
|
||||
|
||||
// 父目录不存在
|
||||
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.Equal(ErrPathNotExist, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 存在同名文件
|
||||
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
|
||||
mock.ExpectQuery("SELECT(.+)files").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.Equal(ErrFileExisted, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 存在同名目录
|
||||
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
|
||||
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.Equal(ErrFolderExisted, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
|
||||
// 成功创建
|
||||
mock.ExpectQuery("SELECT(.+)folders").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "ab"))
|
||||
mock.ExpectQuery("SELECT(.+)files").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}))
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
err = fs.CreateDirectory(ctx, "/ad/ab")
|
||||
asserts.NoError(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
func TestFileSystem_ListDeleteFiles(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
|
||||
// 成功
|
||||
// 查询根目录
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(1, "1.txt").AddRow(2, "2.txt"))
|
||||
err := fs.ListDeleteFiles(context.Background(), []string{"/"})
|
||||
asserts.NoError(err)
|
||||
path := "/"
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
|
||||
exist, folder := fs.IsPathExist(path)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.True(exist)
|
||||
asserts.Equal(uint(1), folder.ID)
|
||||
}
|
||||
|
||||
// 失败
|
||||
// 深层路径
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnError(errors.New("error"))
|
||||
err := fs.ListDeleteFiles(context.Background(), []string{"/"})
|
||||
asserts.Error(err)
|
||||
asserts.Equal(serializer.CodeDBError, err.(serializer.AppError).Code)
|
||||
path := "/1/2/3"
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(1, 1))
|
||||
// 1
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 1, "1").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(2, 1))
|
||||
// 2
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(2, 1, "2").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(3, 1))
|
||||
// 3
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(3, 1, "3").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(4, 1))
|
||||
exist, folder := fs.IsPathExist(path)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
asserts.True(exist)
|
||||
asserts.Equal(uint(4), folder.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_ListDeleteDirs(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
}}
|
||||
|
||||
// 成功
|
||||
// 深层 不存在
|
||||
{
|
||||
path := "/1/2/3"
|
||||
// 根目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(1, 1))
|
||||
// 1
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id", "name"}).
|
||||
AddRow(4, "1.txt").
|
||||
AddRow(5, "2.txt").
|
||||
AddRow(6, "3.txt"),
|
||||
)
|
||||
err := fs.ListDeleteDirs(context.Background(), []string{"/"})
|
||||
asserts.NoError(err)
|
||||
asserts.Len(fs.FileTarget, 3)
|
||||
asserts.Len(fs.DirTarget, 3)
|
||||
WithArgs(1, 1, "1").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(2, 1))
|
||||
// 2
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(2, 1, "2").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}).AddRow(3, 1))
|
||||
// 3
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(3, 1, "3").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "owner_id"}))
|
||||
exist, folder := fs.IsPathExist(path)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 检索文件发生错误
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnError(errors.New("error"))
|
||||
err := fs.ListDeleteDirs(context.Background(), []string{"/"})
|
||||
asserts.Error(err)
|
||||
asserts.Len(fs.DirTarget, 6)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
// 检索目录发生错误
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnError(errors.New("error"))
|
||||
err := fs.ListDeleteDirs(context.Background(), []string{"/"})
|
||||
asserts.Error(err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_Delete(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Storage: 3,
|
||||
Group: model.Group{MaxStorage: 3},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 全部未成功
|
||||
{
|
||||
// 列出要删除的目录
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).
|
||||
AddRow(4, "1.txt", "1.txt", 2, 1),
|
||||
)
|
||||
// 查询顶级的文件
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).AddRow(1, "1.txt", "1.txt", 1, 2))
|
||||
mock.ExpectQuery("SELECT(.+)files(.+)").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "policy_id", "source_name"}))
|
||||
// 查询上传策略
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
// 删除文件记录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)files").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 归还容量
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)users").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 删除目录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)folders").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
|
||||
err := fs.Delete(ctx, []string{"/"}, []string{"2.txt"})
|
||||
asserts.Error(err)
|
||||
asserts.Equal(203, err.(serializer.AppError).Code)
|
||||
asserts.Equal(uint64(0), fs.User.Storage)
|
||||
}
|
||||
// 全部成功
|
||||
{
|
||||
file, err := os.Create("1.txt")
|
||||
file2, err := os.Create("2.txt")
|
||||
file.Close()
|
||||
file2.Close()
|
||||
asserts.NoError(err)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id"}).
|
||||
AddRow(1).
|
||||
AddRow(2).
|
||||
AddRow(3),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").
|
||||
WithArgs(1, 2, 3).
|
||||
WillReturnRows(
|
||||
sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).
|
||||
AddRow(4, "1.txt", "1.txt", 2, 1),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "name", "source_name", "policy_id", "size"}).AddRow(1, "2.txt", "2.txt", 1, 2))
|
||||
mock.ExpectQuery("SELECT(.+)files(.+)").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "policy_id", "source_name"}))
|
||||
// 查询上传策略
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id", "type"}).AddRow(1, "local"))
|
||||
// 删除文件记录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 归还容量
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("UPDATE(.+)").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
// 删除目录
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("DELETE(.+)").
|
||||
WillReturnResult(sqlmock.NewResult(0, 3))
|
||||
mock.ExpectCommit()
|
||||
|
||||
fs.FileTarget = []model.File{}
|
||||
fs.DirTarget = []model.Folder{}
|
||||
err = fs.Delete(ctx, []string{"/"}, []string{"2.txt"})
|
||||
asserts.NoError(err)
|
||||
asserts.Equal(uint64(0), fs.User.Storage)
|
||||
asserts.False(exist)
|
||||
asserts.Nil(folder)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFileSystem_Copy(t *testing.T) {
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Storage: 3,
|
||||
Group: model.Group{MaxStorage: 3},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 目录不存在
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/dst").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/src").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
err := fs.Copy(ctx, []string{}, []string{}, "/src", "/dst")
|
||||
asserts.Equal(ErrPathNotExist, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 复制目录出错
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/src").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/dst").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
err := fs.Copy(ctx, []string{"test"}, []string{}, "/src", "/dst")
|
||||
asserts.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFileSystem_Move(t *testing.T) {
|
||||
conf.DatabaseConfig.Type = "mysql"
|
||||
asserts := assert.New(t)
|
||||
fs := &FileSystem{User: &model.User{
|
||||
Model: gorm.Model{
|
||||
ID: 1,
|
||||
},
|
||||
Storage: 3,
|
||||
Group: model.Group{MaxStorage: 3},
|
||||
}}
|
||||
ctx := context.Background()
|
||||
|
||||
// 目录不存在
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}),
|
||||
)
|
||||
err := fs.Move(ctx, []string{}, []string{}, "/src", "/dst")
|
||||
asserts.Equal(ErrPathNotExist, err)
|
||||
asserts.NoError(mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// 移动目录出错
|
||||
{
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/src").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
mock.ExpectQuery("SELECT(.+)").WithArgs(uint(1), "/dst").WillReturnRows(
|
||||
sqlmock.NewRows([]string{"name"}).AddRow("123"),
|
||||
)
|
||||
err := fs.Move(ctx, []string{"test"}, []string{}, "/src", "/dst")
|
||||
asserts.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,5 +13,19 @@ func FillSlash(path string) string {
|
||||
return path
|
||||
}
|
||||
return path + "/"
|
||||
|
||||
}
|
||||
|
||||
// SplitPath 分割路径为列表
|
||||
func SplitPath(path string) []string {
|
||||
if len(path) == 0 || path[0] != '/' {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
if path == "/" {
|
||||
return []string{"/"}
|
||||
}
|
||||
|
||||
pathSplit := strings.Split(path, "/")
|
||||
pathSplit[0] = "/"
|
||||
return pathSplit
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user