diff --git a/assets b/assets index 3235069f..2e995f1d 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 3235069f05124a39c57a7347dc1cb53ebbd2dad9 +Subproject commit 2e995f1d4676d15b714dd82cb3aefe2e7697fcea diff --git a/pkg/filemanager/manager/manager.go b/pkg/filemanager/manager/manager.go index 17fa18e5..31b2ec7a 100644 --- a/pkg/filemanager/manager/manager.go +++ b/pkg/filemanager/manager/manager.go @@ -43,6 +43,8 @@ type ( Rename(ctx context.Context, path *fs.URI, newName string) (fs.File, error) // Delete deletes a group of file or directory. UnlinkOnly indicates whether to delete file record in DB only. Delete(ctx context.Context, path []*fs.URI, opts ...fs.Option) error + // EmptyTrash hard-deletes every top-level item in the current user's trash bin. + EmptyTrash(ctx context.Context) error // Restore restores a group of files Restore(ctx context.Context, path ...*fs.URI) error // MoveOrCopy moves or copies a group of files diff --git a/pkg/filemanager/manager/operation.go b/pkg/filemanager/manager/operation.go index bb1fdcd3..56f787df 100644 --- a/pkg/filemanager/manager/operation.go +++ b/pkg/filemanager/manager/operation.go @@ -157,6 +157,46 @@ func (m *manager) SoftDelete(ctx context.Context, path ...*fs.URI) error { return m.fs.SoftDelete(ctx, path...) } +// EmptyTrash hard-deletes every top-level item in the current user's trash bin. +// It iterates through all pages of the trash listing and performs the deletion in +// batches. Returns nil if the trash is already empty. +func (m *manager) EmptyTrash(ctx context.Context) error { + trashUri, err := fs.NewUriFromString(fmt.Sprintf("%s://%s", constants.CloudreveScheme, constants.FileSystemTrash)) + if err != nil { + return fmt.Errorf("failed to build trash uri: %w", err) + } + + dbfsSetting := m.settings.DBFS(ctx) + pageSize := dbfsSetting.MaxPageSize + + for { + _, listRes, err := m.fs.List(ctx, trashUri, fs.WithPageSize(pageSize)) + if err != nil { + return fmt.Errorf("failed to list trash: %w", err) + } + + if len(listRes.Files) == 0 { + return nil + } + + uris := make([]*fs.URI, 0, len(listRes.Files)) + for _, f := range listRes.Files { + uris = append(uris, f.Uri(false)) + } + + if err := m.Delete(ctx, uris, fs.WithSysSkipSoftDelete(true)); err != nil { + return fmt.Errorf("failed to delete trash files: %w", err) + } + + // Stop when there are no more pages. + if listRes.Pagination == nil || + (listRes.Pagination.IsCursor && listRes.Pagination.NextPageToken == "") || + (!listRes.Pagination.IsCursor && len(listRes.Files) < pageSize) { + return nil + } + } +} + func (m *manager) Delete(ctx context.Context, path []*fs.URI, opts ...fs.Option) error { o := newOption() for _, opt := range opts { diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 73039788..a956c6b2 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -325,6 +325,18 @@ func Restore(c *gin.Context) { c.JSON(200, serializer.Response{}) } +// EmptyTrash hard-deletes every top-level item in the current user's trash bin. +func EmptyTrash(c *gin.Context) { + err := explorer.EmptyTrash(c) + if err != nil { + c.JSON(200, serializer.Err(c, err)) + c.Abort() + return + } + + c.JSON(200, serializer.Response{}) +} + // Unlock unlocks files by given tokens func Unlock(c *gin.Context) { service := ParametersFromContext[*explorer.UnlockFileService](c, explorer.UnlockFileParameterCtx{}) diff --git a/routers/router.go b/routers/router.go index d1adcbce..1b9b23bf 100644 --- a/routers/router.go +++ b/routers/router.go @@ -671,6 +671,11 @@ func initMasterRouter(dep dependency.Dep) *gin.Engine { middleware.ValidateBatchFileCount(dep, explorer.DeleteFileParameterCtx{}), controllers.Delete, ) + // Empty trash bin + file.DELETE("trash", + middleware.RequiredScopes(types.ScopeFilesWrite), + controllers.EmptyTrash, + ) // Force unlock file.DELETE("lock", middleware.RequiredScopes(types.ScopeFilesWrite), diff --git a/service/explorer/file.go b/service/explorer/file.go index 98dbe63b..0552664f 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -579,6 +579,20 @@ func (s *DeleteFileService) Restore(c *gin.Context) error { return nil } +// EmptyTrash hard-deletes every top-level item in the current user's trash bin. +func EmptyTrash(c *gin.Context) error { + dep := dependency.FromContext(c) + user := inventory.UserFromContext(c) + m := manager.NewFileManager(dep, user) + defer m.Recycle() + + if err := m.EmptyTrash(c); err != nil { + return fmt.Errorf("failed to empty trash: %w", err) + } + + return nil +} + type ( UnlockFileParameterCtx struct{} UnlockFileService struct {