325 lines
7.8 KiB
Go
325 lines
7.8 KiB
Go
package model
|
||
|
||
import (
|
||
"errors"
|
||
"path"
|
||
"time"
|
||
|
||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||
"github.com/jinzhu/gorm"
|
||
)
|
||
|
||
// Folder 目录
|
||
type Folder struct {
|
||
// 表字段
|
||
gorm.Model
|
||
Name string `gorm:"unique_index:idx_only_one_name"`
|
||
ParentID *uint `gorm:"index:parent_id;unique_index:idx_only_one_name"`
|
||
OwnerID uint `gorm:"index:owner_id"`
|
||
|
||
// 数据库忽略字段
|
||
Position string `gorm:"-"`
|
||
}
|
||
|
||
// Create 创建目录
|
||
func (folder *Folder) Create() (uint, error) {
|
||
if err := DB.Create(folder).Error; err != nil {
|
||
util.Log().Warning("无法插入目录记录, %s", err)
|
||
return 0, err
|
||
}
|
||
return folder.ID, nil
|
||
}
|
||
|
||
// GetChild 返回folder下名为name的子目录,不存在则返回错误
|
||
func (folder *Folder) GetChild(name string) (*Folder, error) {
|
||
var resFolder Folder
|
||
err := DB.
|
||
Where("parent_id = ? AND owner_id = ? AND name = ?", folder.ID, folder.OwnerID, name).
|
||
First(&resFolder).Error
|
||
|
||
// 将子目录的路径传递下去
|
||
if err == nil {
|
||
resFolder.Position = path.Join(folder.Position, folder.Name)
|
||
}
|
||
return &resFolder, err
|
||
}
|
||
|
||
// TraceRoot 向上递归查找父目录
|
||
func (folder *Folder) TraceRoot() error {
|
||
if folder.ParentID == nil {
|
||
return nil
|
||
}
|
||
|
||
var parentFolder Folder
|
||
err := DB.
|
||
Where("id = ? AND owner_id = ?", folder.ParentID, folder.OwnerID).
|
||
First(&parentFolder).Error
|
||
|
||
if err == nil {
|
||
err := parentFolder.TraceRoot()
|
||
folder.Position = path.Join(parentFolder.Position, parentFolder.Name)
|
||
return err
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
// GetChildFolder 查找子目录
|
||
func (folder *Folder) GetChildFolder() ([]Folder, error) {
|
||
var folders []Folder
|
||
result := DB.Where("parent_id = ?", folder.ID).Find(&folders)
|
||
|
||
if result.Error == nil {
|
||
for i := 0; i < len(folders); i++ {
|
||
folders[i].Position = path.Join(folder.Position, folder.Name)
|
||
}
|
||
}
|
||
return folders, result.Error
|
||
}
|
||
|
||
// GetRecursiveChildFolder 查找所有递归子目录,包括自身
|
||
func GetRecursiveChildFolder(dirs []uint, uid uint, includeSelf bool) ([]Folder, error) {
|
||
folders := make([]Folder, 0, len(dirs))
|
||
var err error
|
||
|
||
var parFolders []Folder
|
||
result := DB.Where("owner_id = ? and id in (?)", uid, dirs).Find(&parFolders)
|
||
if result.Error != nil {
|
||
return folders, err
|
||
}
|
||
|
||
// 整理父目录的ID
|
||
var parentIDs = make([]uint, 0, len(parFolders))
|
||
for _, folder := range parFolders {
|
||
parentIDs = append(parentIDs, folder.ID)
|
||
}
|
||
|
||
if includeSelf {
|
||
// 合并至最终结果
|
||
folders = append(folders, parFolders...)
|
||
}
|
||
parFolders = []Folder{}
|
||
|
||
// 递归查询子目录,最大递归65535次
|
||
for i := 0; i < 65535; i++ {
|
||
|
||
result = DB.Where("owner_id = ? and parent_id in (?)", uid, parentIDs).Find(&parFolders)
|
||
|
||
// 查询结束条件
|
||
if len(parFolders) == 0 {
|
||
break
|
||
}
|
||
|
||
// 整理父目录的ID
|
||
parentIDs = make([]uint, 0, len(parFolders))
|
||
for _, folder := range parFolders {
|
||
parentIDs = append(parentIDs, folder.ID)
|
||
}
|
||
|
||
// 合并至最终结果
|
||
folders = append(folders, parFolders...)
|
||
parFolders = []Folder{}
|
||
|
||
}
|
||
|
||
return folders, err
|
||
}
|
||
|
||
// DeleteFolderByIDs 根据给定ID批量删除目录记录
|
||
func DeleteFolderByIDs(ids []uint) error {
|
||
result := DB.Where("id in (?)", ids).Unscoped().Delete(&Folder{})
|
||
return result.Error
|
||
}
|
||
|
||
// GetFoldersByIDs 根据ID和用户查找所有目录
|
||
func GetFoldersByIDs(ids []uint, uid uint) ([]Folder, error) {
|
||
var folders []Folder
|
||
result := DB.Where("id in (?) AND owner_id = ?", ids, uid).Find(&folders)
|
||
return folders, result.Error
|
||
}
|
||
|
||
// MoveOrCopyFileTo 将此目录下的files移动或复制至dstFolder,
|
||
// 返回此操作新增的容量
|
||
func (folder *Folder) MoveOrCopyFileTo(files []uint, dstFolder *Folder, isCopy bool) (uint64, error) {
|
||
// 已复制文件的总大小
|
||
var copiedSize uint64
|
||
|
||
if isCopy {
|
||
// 检索出要复制的文件
|
||
var originFiles = make([]File, 0, len(files))
|
||
if err := DB.Where(
|
||
"id in (?) and user_id = ? and folder_id = ?",
|
||
files,
|
||
folder.OwnerID,
|
||
folder.ID,
|
||
).Find(&originFiles).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
// 复制文件记录
|
||
for _, oldFile := range originFiles {
|
||
if !oldFile.CanCopy() {
|
||
util.Log().Warning("无法复制正在上传中的文件 [%s], 跳过...", oldFile.Name)
|
||
continue
|
||
}
|
||
|
||
oldFile.Model = gorm.Model{}
|
||
oldFile.FolderID = dstFolder.ID
|
||
oldFile.UserID = dstFolder.OwnerID
|
||
|
||
if err := DB.Create(&oldFile).Error; err != nil {
|
||
return copiedSize, err
|
||
}
|
||
|
||
copiedSize += oldFile.Size
|
||
}
|
||
|
||
} else {
|
||
// 更改顶级要移动文件的父目录指向
|
||
err := DB.Model(File{}).Where(
|
||
"id in (?) and user_id = ? and folder_id = ?",
|
||
files,
|
||
folder.OwnerID,
|
||
folder.ID,
|
||
).
|
||
Update(map[string]interface{}{
|
||
"folder_id": dstFolder.ID,
|
||
}).
|
||
Error
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
}
|
||
|
||
return copiedSize, nil
|
||
|
||
}
|
||
|
||
// CopyFolderTo 将此目录及其子目录及文件递归复制至dstFolder
|
||
// 返回此操作新增的容量
|
||
func (folder *Folder) CopyFolderTo(folderID uint, dstFolder *Folder) (size uint64, err error) {
|
||
// 列出所有子目录
|
||
subFolders, err := GetRecursiveChildFolder([]uint{folderID}, folder.OwnerID, true)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
// 抽离所有子目录的ID
|
||
var subFolderIDs = make([]uint, len(subFolders))
|
||
for key, value := range subFolders {
|
||
subFolderIDs[key] = value.ID
|
||
}
|
||
|
||
// 复制子目录
|
||
var newIDCache = make(map[uint]uint)
|
||
for _, folder := range subFolders {
|
||
// 新的父目录指向
|
||
var newID uint
|
||
// 顶级目录直接指向新的目的目录
|
||
if folder.ID == folderID {
|
||
newID = dstFolder.ID
|
||
} else if IDCache, ok := newIDCache[*folder.ParentID]; ok {
|
||
newID = IDCache
|
||
} else {
|
||
util.Log().Warning("无法取得新的父目录:%d", folder.ParentID)
|
||
return size, errors.New("无法取得新的父目录")
|
||
}
|
||
|
||
// 插入新的目录记录
|
||
oldID := folder.ID
|
||
folder.Model = gorm.Model{}
|
||
folder.ParentID = &newID
|
||
folder.OwnerID = dstFolder.OwnerID
|
||
if err = DB.Create(&folder).Error; err != nil {
|
||
return size, err
|
||
}
|
||
// 记录新的ID以便其子目录使用
|
||
newIDCache[oldID] = folder.ID
|
||
|
||
}
|
||
|
||
// 复制文件
|
||
var originFiles = make([]File, 0, len(subFolderIDs))
|
||
if err := DB.Where(
|
||
"user_id = ? and folder_id in (?)",
|
||
folder.OwnerID,
|
||
subFolderIDs,
|
||
).Find(&originFiles).Error; err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
// 复制文件记录
|
||
for _, oldFile := range originFiles {
|
||
if !oldFile.CanCopy() {
|
||
util.Log().Warning("无法复制正在上传中的文件 [%s], 跳过...", oldFile.Name)
|
||
continue
|
||
}
|
||
|
||
oldFile.Model = gorm.Model{}
|
||
oldFile.FolderID = newIDCache[oldFile.FolderID]
|
||
oldFile.UserID = dstFolder.OwnerID
|
||
if err := DB.Create(&oldFile).Error; err != nil {
|
||
return size, err
|
||
}
|
||
|
||
size += oldFile.Size
|
||
}
|
||
|
||
return size, nil
|
||
|
||
}
|
||
|
||
// MoveFolderTo 将folder目录下的dirs子目录复制或移动到dstFolder,
|
||
// 返回此过程中增加的容量
|
||
func (folder *Folder) MoveFolderTo(dirs []uint, dstFolder *Folder) error {
|
||
|
||
// 如果目标位置为待移动的目录,会导致 parent 为自己
|
||
// 造成死循环且无法被除搜索以外的组件展示
|
||
if folder.OwnerID == dstFolder.OwnerID && util.ContainsUint(dirs, dstFolder.ID) {
|
||
return errors.New("cannot move a folder into itself")
|
||
}
|
||
|
||
// 更改顶级要移动目录的父目录指向
|
||
err := DB.Model(Folder{}).Where(
|
||
"id in (?) and owner_id = ? and parent_id = ?",
|
||
dirs,
|
||
folder.OwnerID,
|
||
folder.ID,
|
||
).Update(map[string]interface{}{
|
||
"parent_id": dstFolder.ID,
|
||
}).Error
|
||
|
||
return err
|
||
|
||
}
|
||
|
||
// Rename 重命名目录
|
||
func (folder *Folder) Rename(new string) error {
|
||
if err := DB.Model(&folder).Update("name", new).Error; err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
/*
|
||
实现 FileInfo.FileInfo 接口
|
||
TODO 测试
|
||
*/
|
||
|
||
func (folder *Folder) GetName() string {
|
||
return folder.Name
|
||
}
|
||
|
||
func (folder *Folder) GetSize() uint64 {
|
||
return 0
|
||
}
|
||
func (folder *Folder) ModTime() time.Time {
|
||
return folder.UpdatedAt
|
||
}
|
||
func (folder *Folder) IsDir() bool {
|
||
return true
|
||
}
|
||
func (folder *Folder) GetPosition() string {
|
||
return folder.Position
|
||
}
|