github.com/cloudreve/Cloudreve/v3@v3.0.0-20240224133659-3edb00a6484c/pkg/filesystem/manage.go (about) 1 package filesystem 2 3 import ( 4 "context" 5 "fmt" 6 "path" 7 "strings" 8 9 model "github.com/cloudreve/Cloudreve/v3/models" 10 "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" 11 "github.com/cloudreve/Cloudreve/v3/pkg/hashid" 12 "github.com/cloudreve/Cloudreve/v3/pkg/serializer" 13 "github.com/cloudreve/Cloudreve/v3/pkg/util" 14 ) 15 16 /* ================= 17 文件/目录管理 18 ================= 19 */ 20 21 // Rename 重命名对象 22 func (fs *FileSystem) Rename(ctx context.Context, dir, file []uint, new string) (err error) { 23 // 验证新名字 24 if !fs.ValidateLegalName(ctx, new) || (len(file) > 0 && !fs.ValidateExtension(ctx, new)) { 25 return ErrIllegalObjectName 26 } 27 28 // 如果源对象是文件 29 if len(file) > 0 { 30 fileObject, err := model.GetFilesByIDs([]uint{file[0]}, fs.User.ID) 31 if err != nil || len(fileObject) == 0 { 32 return ErrPathNotExist 33 } 34 35 err = fileObject[0].Rename(new) 36 if err != nil { 37 return ErrFileExisted 38 } 39 return nil 40 } 41 42 if len(dir) > 0 { 43 folderObject, err := model.GetFoldersByIDs([]uint{dir[0]}, fs.User.ID) 44 if err != nil || len(folderObject) == 0 { 45 return ErrPathNotExist 46 } 47 48 err = folderObject[0].Rename(new) 49 if err != nil { 50 return ErrFileExisted 51 } 52 return nil 53 } 54 55 return ErrPathNotExist 56 } 57 58 // Copy 复制src目录下的文件或目录到dst, 59 // 暂时只支持单文件 60 func (fs *FileSystem) Copy(ctx context.Context, dirs, files []uint, src, dst string) error { 61 // 获取目的目录 62 isDstExist, dstFolder := fs.IsPathExist(dst) 63 isSrcExist, srcFolder := fs.IsPathExist(src) 64 // 不存在时返回空的结果 65 if !isDstExist || !isSrcExist { 66 return ErrPathNotExist 67 } 68 69 // 记录复制的文件的总容量 70 var newUsedStorage uint64 71 72 // 设置webdav目标名 73 if dstName, ok := ctx.Value(fsctx.WebdavDstName).(string); ok { 74 dstFolder.WebdavDstName = dstName 75 } 76 77 // 复制目录 78 if len(dirs) > 0 { 79 subFileSizes, err := srcFolder.CopyFolderTo(dirs[0], dstFolder) 80 if err != nil { 81 return ErrObjectNotExist.WithError(err) 82 } 83 newUsedStorage += subFileSizes 84 } 85 86 // 复制文件 87 if len(files) > 0 { 88 subFileSizes, err := srcFolder.MoveOrCopyFileTo(files, dstFolder, true) 89 if err != nil { 90 return ErrObjectNotExist.WithError(err) 91 } 92 newUsedStorage += subFileSizes 93 } 94 95 // 扣除容量 96 fs.User.IncreaseStorageWithoutCheck(newUsedStorage) 97 98 return nil 99 } 100 101 // Move 移动文件和目录, 将id列表dirs和files从src移动至dst 102 func (fs *FileSystem) Move(ctx context.Context, dirs, files []uint, src, dst string) error { 103 // 获取目的目录 104 isDstExist, dstFolder := fs.IsPathExist(dst) 105 isSrcExist, srcFolder := fs.IsPathExist(src) 106 // 不存在时返回空的结果 107 if !isDstExist || !isSrcExist { 108 return ErrPathNotExist 109 } 110 111 // 设置webdav目标名 112 if dstName, ok := ctx.Value(fsctx.WebdavDstName).(string); ok { 113 dstFolder.WebdavDstName = dstName 114 } 115 116 // 处理目录及子文件移动 117 err := srcFolder.MoveFolderTo(dirs, dstFolder) 118 if err != nil { 119 return ErrFileExisted.WithError(err) 120 } 121 122 // 处理文件移动 123 _, err = srcFolder.MoveOrCopyFileTo(files, dstFolder, false) 124 if err != nil { 125 return ErrFileExisted.WithError(err) 126 } 127 128 // 移动文件 129 130 return err 131 } 132 133 // Delete 递归删除对象, force 为 true 时强制删除文件记录,忽略物理删除是否成功; 134 // unlink 为 true 时只删除虚拟文件系统的文件记录,不删除物理文件。 135 func (fs *FileSystem) Delete(ctx context.Context, dirs, files []uint, force, unlink bool) error { 136 // 已删除的文件ID 137 var deletedFiles = make([]*model.File, 0, len(fs.FileTarget)) 138 // 删除失败的文件的父目录ID 139 140 // 所有文件的ID 141 var allFiles = make([]*model.File, 0, len(fs.FileTarget)) 142 143 // 列出要删除的目录 144 if len(dirs) > 0 { 145 err := fs.ListDeleteDirs(ctx, dirs) 146 if err != nil { 147 return err 148 } 149 } 150 151 // 列出要删除的文件 152 if len(files) > 0 { 153 err := fs.ListDeleteFiles(ctx, files) 154 if err != nil { 155 return err 156 } 157 } 158 159 // 去除待删除文件中包含软连接的部分 160 filesToBeDelete, err := model.RemoveFilesWithSoftLinks(fs.FileTarget) 161 if err != nil { 162 return ErrDBListObjects.WithError(err) 163 } 164 165 // 根据存储策略将文件分组 166 policyGroup := fs.GroupFileByPolicy(ctx, filesToBeDelete) 167 168 // 按照存储策略分组删除对象 169 failed := make(map[uint][]string) 170 if !unlink { 171 failed = fs.deleteGroupedFile(ctx, policyGroup) 172 } 173 174 // 整理删除结果 175 for i := 0; i < len(fs.FileTarget); i++ { 176 if !util.ContainsString(failed[fs.FileTarget[i].PolicyID], fs.FileTarget[i].SourceName) { 177 // 已成功删除的文件 178 deletedFiles = append(deletedFiles, &fs.FileTarget[i]) 179 } 180 181 // 全部文件 182 allFiles = append(allFiles, &fs.FileTarget[i]) 183 } 184 185 // 如果强制删除,则将全部文件视为删除成功 186 if force { 187 deletedFiles = allFiles 188 } 189 190 // 删除文件记录 191 err = model.DeleteFiles(deletedFiles, fs.User.ID) 192 if err != nil { 193 return ErrDBDeleteObjects.WithError(err) 194 } 195 196 // 删除文件记录对应的分享记录 197 // TODO 先取消分享再删除文件 198 deletedFileIDs := make([]uint, len(deletedFiles)) 199 for k, file := range deletedFiles { 200 deletedFileIDs[k] = file.ID 201 } 202 203 model.DeleteShareBySourceIDs(deletedFileIDs, false) 204 205 // 如果文件全部删除成功,继续删除目录 206 if len(deletedFiles) == len(allFiles) { 207 var allFolderIDs = make([]uint, 0, len(fs.DirTarget)) 208 for _, value := range fs.DirTarget { 209 allFolderIDs = append(allFolderIDs, value.ID) 210 } 211 err = model.DeleteFolderByIDs(allFolderIDs) 212 if err != nil { 213 return ErrDBDeleteObjects.WithError(err) 214 } 215 216 // 删除目录记录对应的分享记录 217 model.DeleteShareBySourceIDs(allFolderIDs, true) 218 } 219 220 if notDeleted := len(fs.FileTarget) - len(deletedFiles); notDeleted > 0 { 221 return serializer.NewError( 222 serializer.CodeNotFullySuccess, 223 fmt.Sprintf("Failed to delete %d file(s).", notDeleted), 224 nil, 225 ) 226 } 227 228 return nil 229 } 230 231 // ListDeleteDirs 递归列出要删除目录,及目录下所有文件 232 func (fs *FileSystem) ListDeleteDirs(ctx context.Context, ids []uint) error { 233 // 列出所有递归子目录 234 folders, err := model.GetRecursiveChildFolder(ids, fs.User.ID, true) 235 if err != nil { 236 return ErrDBListObjects.WithError(err) 237 } 238 239 // 忽略根目录 240 for i := 0; i < len(folders); i++ { 241 if folders[i].ParentID == nil { 242 folders = append(folders[:i], folders[i+1:]...) 243 break 244 } 245 } 246 247 fs.SetTargetDir(&folders) 248 249 // 检索目录下的子文件 250 files, err := model.GetChildFilesOfFolders(&folders) 251 if err != nil { 252 return ErrDBListObjects.WithError(err) 253 } 254 fs.SetTargetFile(&files) 255 256 return nil 257 } 258 259 // ListDeleteFiles 根据给定的路径列出要删除的文件 260 func (fs *FileSystem) ListDeleteFiles(ctx context.Context, ids []uint) error { 261 files, err := model.GetFilesByIDs(ids, fs.User.ID) 262 if err != nil { 263 return ErrDBListObjects.WithError(err) 264 } 265 fs.SetTargetFile(&files) 266 return nil 267 } 268 269 // List 列出路径下的内容, 270 // pathProcessor为最终对象路径的处理钩子。 271 // 有些情况下(如在分享页面列对象)时, 272 // 路径需要截取掉被分享目录路径之前的部分。 273 func (fs *FileSystem) List(ctx context.Context, dirPath string, pathProcessor func(string) string) ([]serializer.Object, error) { 274 // 获取父目录 275 isExist, folder := fs.IsPathExist(dirPath) 276 if !isExist { 277 return nil, ErrPathNotExist 278 } 279 fs.SetTargetDir(&[]model.Folder{*folder}) 280 281 var parentPath = path.Join(folder.Position, folder.Name) 282 var childFolders []model.Folder 283 var childFiles []model.File 284 285 // 获取子目录 286 childFolders, _ = folder.GetChildFolder() 287 288 // 获取子文件 289 childFiles, _ = folder.GetChildFiles() 290 291 return fs.listObjects(ctx, parentPath, childFiles, childFolders, pathProcessor), nil 292 } 293 294 // ListPhysical 列出存储策略中的外部目录 295 // TODO:测试 296 func (fs *FileSystem) ListPhysical(ctx context.Context, dirPath string) ([]serializer.Object, error) { 297 if err := fs.DispatchHandler(); fs.Policy == nil || err != nil { 298 return nil, ErrUnknownPolicyType 299 } 300 301 // 存储策略不支持列取时,返回空结果 302 if !fs.Policy.CanStructureBeListed() { 303 return nil, nil 304 } 305 306 // 列取路径 307 objects, err := fs.Handler.List(ctx, dirPath, false) 308 if err != nil { 309 return nil, err 310 } 311 312 var ( 313 folders []model.Folder 314 ) 315 for _, object := range objects { 316 if object.IsDir { 317 folders = append(folders, model.Folder{ 318 Name: object.Name, 319 }) 320 } 321 } 322 323 return fs.listObjects(ctx, dirPath, nil, folders, nil), nil 324 } 325 326 func (fs *FileSystem) listObjects(ctx context.Context, parent string, files []model.File, folders []model.Folder, pathProcessor func(string) string) []serializer.Object { 327 // 分享文件的ID 328 shareKey := "" 329 if key, ok := ctx.Value(fsctx.ShareKeyCtx).(string); ok { 330 shareKey = key 331 } 332 333 // 汇总处理结果 334 objects := make([]serializer.Object, 0, len(files)+len(folders)) 335 336 // 所有对象的父目录 337 var processedPath string 338 339 for _, subFolder := range folders { 340 // 路径处理钩子, 341 // 所有对象父目录都是一样的,所以只处理一次 342 if processedPath == "" { 343 if pathProcessor != nil { 344 processedPath = pathProcessor(parent) 345 } else { 346 processedPath = parent 347 } 348 } 349 350 objects = append(objects, serializer.Object{ 351 ID: hashid.HashID(subFolder.ID, hashid.FolderID), 352 Name: subFolder.Name, 353 Path: processedPath, 354 Size: 0, 355 Type: "dir", 356 Date: subFolder.UpdatedAt, 357 CreateDate: subFolder.CreatedAt, 358 }) 359 } 360 361 for _, file := range files { 362 if processedPath == "" { 363 if pathProcessor != nil { 364 processedPath = pathProcessor(parent) 365 } else { 366 processedPath = parent 367 } 368 } 369 370 if file.UploadSessionID == nil { 371 newFile := serializer.Object{ 372 ID: hashid.HashID(file.ID, hashid.FileID), 373 Name: file.Name, 374 Path: processedPath, 375 Thumb: file.ShouldLoadThumb(), 376 Size: file.Size, 377 Type: "file", 378 Date: file.UpdatedAt, 379 SourceEnabled: file.GetPolicy().IsOriginLinkEnable, 380 CreateDate: file.CreatedAt, 381 } 382 if shareKey != "" { 383 newFile.Key = shareKey 384 } 385 objects = append(objects, newFile) 386 } 387 } 388 389 return objects 390 } 391 392 // CreateDirectory 根据给定的完整创建目录,支持递归创建。如果目录已存在,则直接 393 // 返回已存在的目录。 394 func (fs *FileSystem) CreateDirectory(ctx context.Context, fullPath string) (*model.Folder, error) { 395 if fullPath == "." || fullPath == "" { 396 return nil, ErrRootProtected 397 } 398 399 if fullPath == "/" { 400 if fs.Root != nil { 401 return fs.Root, nil 402 } 403 return fs.User.Root() 404 } 405 406 // 获取要创建目录的父路径和目录名 407 fullPath = path.Clean(fullPath) 408 base := path.Dir(fullPath) 409 dir := path.Base(fullPath) 410 411 // 去掉结尾空格 412 dir = strings.TrimRight(dir, " ") 413 414 // 检查目录名是否合法 415 if !fs.ValidateLegalName(ctx, dir) { 416 return nil, ErrIllegalObjectName 417 } 418 419 // 父目录是否存在 420 isExist, parent := fs.IsPathExist(base) 421 if !isExist { 422 newParent, err := fs.CreateDirectory(ctx, base) 423 if err != nil { 424 return nil, err 425 } 426 parent = newParent 427 } 428 429 // 是否有同名文件 430 if ok, _ := fs.IsChildFileExist(parent, dir); ok { 431 return nil, ErrFileExisted 432 } 433 434 // 创建目录 435 newFolder := model.Folder{ 436 Name: dir, 437 ParentID: &parent.ID, 438 OwnerID: fs.User.ID, 439 } 440 _, err := newFolder.Create() 441 442 if err != nil { 443 return nil, fmt.Errorf("failed to create folder: %w", err) 444 } 445 446 return &newFolder, nil 447 } 448 449 // SaveTo 将别人分享的文件转存到目标路径下 450 func (fs *FileSystem) SaveTo(ctx context.Context, path string) error { 451 // 获取父目录 452 isExist, folder := fs.IsPathExist(path) 453 if !isExist { 454 return ErrPathNotExist 455 } 456 457 var ( 458 totalSize uint64 459 err error 460 ) 461 462 if len(fs.DirTarget) > 0 { 463 totalSize, err = fs.DirTarget[0].CopyFolderTo(fs.DirTarget[0].ID, folder) 464 } else { 465 parent := model.Folder{ 466 OwnerID: fs.FileTarget[0].UserID, 467 } 468 parent.ID = fs.FileTarget[0].FolderID 469 totalSize, err = parent.MoveOrCopyFileTo([]uint{fs.FileTarget[0].ID}, folder, true) 470 } 471 472 // 扣除用户容量 473 fs.User.IncreaseStorageWithoutCheck(totalSize) 474 if err != nil { 475 return ErrFileExisted.WithError(err) 476 } 477 478 return nil 479 }