Feat: use database transactions to delete / update file size

pull/1107/head
HFO4 3 years ago
parent 2811ee3285
commit 285e80ba76

@ -3,6 +3,7 @@ package model
import ( import (
"encoding/gob" "encoding/gob"
"encoding/json" "encoding/json"
"errors"
"path" "path"
"time" "time"
@ -200,10 +201,35 @@ func RemoveFilesWithSoftLinks(files []File) ([]File, error) {
} }
// DeleteFileByIDs 根据给定ID批量删除文件记录 // DeleteFiles 批量删除文件记录并归还容量
func DeleteFileByIDs(ids []uint) error { func DeleteFiles(files []*File, uid uint) error {
result := DB.Where("id in (?)", ids).Unscoped().Delete(&File{}) tx := DB.Begin()
return result.Error user := &User{}
user.ID = uid
var size uint64
for _, file := range files {
if file.UserID != uid {
tx.Rollback()
return errors.New("User id not consistent")
}
result := tx.Unscoped().Delete(file)
if result.RowsAffected != 0 {
size += file.Size
}
if result.Error != nil {
tx.Rollback()
return result.Error
}
}
if err := user.ChangeStorage(tx, "-", size); err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
} }
// GetFilesByParentIDs 根据父目录ID查找文件 // GetFilesByParentIDs 根据父目录ID查找文件
@ -232,7 +258,29 @@ func (file *File) UpdatePicInfo(value string) error {
// UpdateSize 更新文件的大小信息 // UpdateSize 更新文件的大小信息
func (file *File) UpdateSize(value uint64) error { func (file *File) UpdateSize(value uint64) error {
return DB.Model(&file).Set("gorm:association_autoupdate", false).Update("size", value).Error tx := DB.Begin()
var sizeDelta uint64
operator := "+"
user := User{}
user.ID = file.UserID
if value > file.Size {
sizeDelta = value - file.Size
} else {
operator = "-"
sizeDelta = file.Size - value
}
if res := tx.Model(&file).Set("gorm:association_autoupdate", false).Update("size", value); res.Error != nil {
tx.Rollback()
return res.Error
}
if err := user.ChangeStorage(tx, operator, sizeDelta); err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
} }
// UpdateSourceName 更新文件的源文件名 // UpdateSourceName 更新文件的源文件名

@ -89,6 +89,11 @@ func (user *User) IncreaseStorage(size uint64) bool {
return false return false
} }
// ChangeStorage 更新用户容量
func (user *User) ChangeStorage(tx *gorm.DB, operator string, size uint64) error {
return tx.Model(user).Update("storage", gorm.Expr("storage "+operator+" ?", size)).Error
}
// IncreaseStorageWithoutCheck 忽略可用容量,增加用户已用容量 // IncreaseStorageWithoutCheck 忽略可用容量,增加用户已用容量
func (user *User) IncreaseStorageWithoutCheck(size uint64) { func (user *User) IncreaseStorageWithoutCheck(size uint64) {
if size == 0 { if size == 0 {

@ -133,19 +133,15 @@ func HookValidateCapacityWithoutIncrease(ctx context.Context, fs *FileSystem, fi
return nil return nil
} }
// HookChangeCapacity 根据原有文件和新文件的大小更新用户容量 // HookValidateCapacityDiff 根据原有文件和新文件的大小验证用户容量
func HookChangeCapacity(ctx context.Context, fs *FileSystem, newFile fsctx.FileHeader) error { func HookValidateCapacityDiff(ctx context.Context, fs *FileSystem, newFile fsctx.FileHeader) error {
originFile := ctx.Value(fsctx.FileModelCtx).(model.File) originFile := ctx.Value(fsctx.FileModelCtx).(model.File)
newFileSize := newFile.Info().Size newFileSize := newFile.Info().Size
if newFileSize > originFile.Size { if newFileSize > originFile.Size {
if !fs.ValidateCapacity(ctx, newFileSize-originFile.Size) { return HookValidateCapacityWithoutIncrease(ctx, fs, newFile)
return ErrInsufficientCapacity
}
return nil
} }
fs.User.DeductionStorage(originFile.Size - newFileSize)
return nil return nil
} }

@ -122,15 +122,12 @@ func (fs *FileSystem) Move(ctx context.Context, dirs, files []uint, src, dst str
// Delete 递归删除对象, force 为 true 时强制删除文件记录,忽略物理删除是否成功 // Delete 递归删除对象, force 为 true 时强制删除文件记录,忽略物理删除是否成功
func (fs *FileSystem) Delete(ctx context.Context, dirs, files []uint, force bool) error { func (fs *FileSystem) Delete(ctx context.Context, dirs, files []uint, force bool) error {
// 已删除的总容量,map用于去重
var deletedStorage = make(map[uint]uint64)
var totalStorage = make(map[uint]uint64)
// 已删除的文件ID // 已删除的文件ID
var deletedFileIDs = make([]uint, 0, len(fs.FileTarget)) var deletedFiles = make([]*model.File, 0, len(fs.FileTarget))
// 删除失败的文件的父目录ID // 删除失败的文件的父目录ID
// 所有文件的ID // 所有文件的ID
var allFileIDs = make([]uint, 0, len(fs.FileTarget)) var allFiles = make([]*model.File, 0, len(fs.FileTarget))
// 列出要删除的目录 // 列出要删除的目录
if len(dirs) > 0 { if len(dirs) > 0 {
@ -164,39 +161,35 @@ func (fs *FileSystem) Delete(ctx context.Context, dirs, files []uint, force bool
for i := 0; i < len(fs.FileTarget); i++ { for i := 0; i < len(fs.FileTarget); i++ {
if !util.ContainsString(failed[fs.FileTarget[i].PolicyID], fs.FileTarget[i].SourceName) { if !util.ContainsString(failed[fs.FileTarget[i].PolicyID], fs.FileTarget[i].SourceName) {
// 已成功删除的文件 // 已成功删除的文件
deletedFileIDs = append(deletedFileIDs, fs.FileTarget[i].ID) deletedFiles = append(deletedFiles, &fs.FileTarget[i])
deletedStorage[fs.FileTarget[i].ID] = fs.FileTarget[i].Size
} }
// 全部文件 // 全部文件
totalStorage[fs.FileTarget[i].ID] = fs.FileTarget[i].Size allFiles = append(allFiles, &fs.FileTarget[i])
allFileIDs = append(allFileIDs, fs.FileTarget[i].ID)
} }
// 如果强制删除,则将全部文件视为删除成功 // 如果强制删除,则将全部文件视为删除成功
if force { if force {
deletedFileIDs = allFileIDs deletedFiles = allFiles
deletedStorage = totalStorage
} }
// 删除文件记录 // 删除文件记录
err = model.DeleteFileByIDs(deletedFileIDs) err = model.DeleteFiles(deletedFiles, fs.User.ID)
if err != nil { if err != nil {
return ErrDBDeleteObjects.WithError(err) return ErrDBDeleteObjects.WithError(err)
} }
// 删除文件记录对应的分享记录 // 删除文件记录对应的分享记录
// TODO 先取消分享再删除文件 // TODO 先取消分享再删除文件
model.DeleteShareBySourceIDs(deletedFileIDs, false) deletedFileIDs := make([]uint, len(deletedFiles))
for k, file := range deletedFiles {
// 归还容量 deletedFileIDs[k] = file.ID
var total uint64
for _, value := range deletedStorage {
total += value
} }
fs.User.DeductionStorage(total)
model.DeleteShareBySourceIDs(deletedFileIDs, false)
// 如果文件全部删除成功,继续删除目录 // 如果文件全部删除成功,继续删除目录
if len(deletedFileIDs) == len(allFileIDs) { if len(deletedFiles) == len(allFiles) {
var allFolderIDs = make([]uint, 0, len(fs.DirTarget)) var allFolderIDs = make([]uint, 0, len(fs.DirTarget))
for _, value := range fs.DirTarget { for _, value := range fs.DirTarget {
allFolderIDs = append(allFolderIDs, value.ID) allFolderIDs = append(allFolderIDs, value.ID)
@ -210,7 +203,7 @@ func (fs *FileSystem) Delete(ctx context.Context, dirs, files []uint, force bool
model.DeleteShareBySourceIDs(allFolderIDs, true) model.DeleteShareBySourceIDs(allFolderIDs, true)
} }
if notDeleted := len(fs.FileTarget) - len(deletedFileIDs); notDeleted > 0 { if notDeleted := len(fs.FileTarget) - len(deletedFiles); notDeleted > 0 {
return serializer.NewError( return serializer.NewError(
serializer.CodeNotFullySuccess, serializer.CodeNotFullySuccess,
fmt.Sprintf("有 %d 个文件未能成功删除", notDeleted), fmt.Sprintf("有 %d 个文件未能成功删除", notDeleted),

@ -349,15 +349,13 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request, fs *filesyst
fs.Use("BeforeUpload", filesystem.HookResetPolicy) fs.Use("BeforeUpload", filesystem.HookResetPolicy)
fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookValidateFile)
fs.Use("BeforeUpload", filesystem.HookChangeCapacity) fs.Use("BeforeUpload", filesystem.HookValidateCapacityDiff)
fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent) fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent)
fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize) fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize)
fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
fs.Use("AfterUploadCanceled", filesystem.HookCancelContext) fs.Use("AfterUploadCanceled", filesystem.HookCancelContext)
fs.Use("AfterUpload", filesystem.GenericAfterUpdate) fs.Use("AfterUpload", filesystem.GenericAfterUpdate)
fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent) fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent)
fs.Use("AfterValidateFailed", filesystem.HookClearFileSize) fs.Use("AfterValidateFailed", filesystem.HookClearFileSize)
fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
ctx = context.WithValue(ctx, fsctx.FileModelCtx, *originFile) ctx = context.WithValue(ctx, fsctx.FileModelCtx, *originFile)
} else { } else {
// 给文件系统分配钩子 // 给文件系统分配钩子

@ -405,14 +405,12 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se
// 给文件系统分配钩子 // 给文件系统分配钩子
fs.Use("BeforeUpload", filesystem.HookResetPolicy) fs.Use("BeforeUpload", filesystem.HookResetPolicy)
fs.Use("BeforeUpload", filesystem.HookValidateFile) fs.Use("BeforeUpload", filesystem.HookValidateFile)
fs.Use("BeforeUpload", filesystem.HookChangeCapacity) fs.Use("BeforeUpload", filesystem.HookValidateCapacityDiff)
fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent) fs.Use("AfterUploadCanceled", filesystem.HookCleanFileContent)
fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize) fs.Use("AfterUploadCanceled", filesystem.HookClearFileSize)
fs.Use("AfterUploadCanceled", filesystem.HookGiveBackCapacity)
fs.Use("AfterUpload", filesystem.GenericAfterUpdate) fs.Use("AfterUpload", filesystem.GenericAfterUpdate)
fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent) fs.Use("AfterValidateFailed", filesystem.HookCleanFileContent)
fs.Use("AfterValidateFailed", filesystem.HookClearFileSize) fs.Use("AfterValidateFailed", filesystem.HookClearFileSize)
fs.Use("AfterValidateFailed", filesystem.HookGiveBackCapacity)
// 执行上传 // 执行上传
uploadCtx = context.WithValue(uploadCtx, fsctx.FileModelCtx, originFile[0]) uploadCtx = context.WithValue(uploadCtx, fsctx.FileModelCtx, originFile[0])

Loading…
Cancel
Save