package dbfs import ( "context" "fmt" "github.com/cloudreve/Cloudreve/v4/inventory" "github.com/cloudreve/Cloudreve/v4/inventory/types" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" "github.com/cloudreve/Cloudreve/v4/pkg/serializer" "github.com/samber/lo" ) func (f *DBFS) PatchProps(ctx context.Context, uri *fs.URI, props *types.FileProps, delete bool) error { navigator, err := f.getNavigator(ctx, uri, NavigatorCapabilityModifyProps, NavigatorCapabilityLockFile) if err != nil { return err } target, err := f.getFileByPath(ctx, navigator, uri) if err != nil { return fmt.Errorf("failed to get target file: %w", err) } if target.OwnerID() != f.user.ID && !f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) { return fs.ErrOwnerOnly.WithError(fmt.Errorf("only file owner can modify file props")) } // Lock target lr := &LockByPath{target.Uri(true), target, target.Type(), ""} ls, err := f.acquireByPath(ctx, -1, f.user, true, fs.LockApp(fs.ApplicationUpdateMetadata), lr) defer func() { _ = f.Release(ctx, ls) }() if err != nil { return err } currentProps := target.Model.Props if currentProps == nil { currentProps = &types.FileProps{} } if props.View != nil { if delete { currentProps.View = nil } else { currentProps.View = props.View } } if _, err := f.fileClient.UpdateProps(ctx, target.Model, currentProps); err != nil { return serializer.NewError(serializer.CodeDBError, "failed to update file props", err) } return nil } func (f *DBFS) PatchMetadata(ctx context.Context, path []*fs.URI, metas ...fs.MetadataPatch) error { ae := serializer.NewAggregateError() targets := make([]*File, 0, len(path)) for _, p := range path { navigator, err := f.getNavigator(ctx, p, NavigatorCapabilityUpdateMetadata, NavigatorCapabilityLockFile) if err != nil { ae.Add(p.String(), err) continue } target, err := f.getFileByPath(ctx, navigator, p) if err != nil { ae.Add(p.String(), fmt.Errorf("failed to get target file: %w", err)) continue } // Require Update permission if _, ok := ctx.Value(ByPassOwnerCheckCtxKey{}).(bool); !ok && target.OwnerID() != f.user.ID { return fs.ErrOwnerOnly.WithError(fmt.Errorf("permission denied")) } if target.IsRootFolder() { ae.Add(p.String(), fs.ErrNotSupportedAction.WithError(fmt.Errorf("cannot move root folder"))) continue } targets = append(targets, target) } if len(targets) == 0 { return ae.Aggregate() } // Lock all targets lockTargets := lo.Map(targets, func(value *File, key int) *LockByPath { return &LockByPath{value.Uri(true), value, value.Type(), ""} }) ls, err := f.acquireByPath(ctx, -1, f.user, true, fs.LockApp(fs.ApplicationUpdateMetadata), lockTargets...) defer func() { _ = f.Release(ctx, ls) }() if err != nil { return err } metadataMap := make(map[string]string) privateMap := make(map[string]bool) deleted := make([]string, 0) for _, meta := range metas { if meta.Remove { deleted = append(deleted, meta.Key) continue } metadataMap[meta.Key] = meta.Value if meta.Private { privateMap[meta.Key] = meta.Private } } fc, tx, ctx, err := inventory.WithTx(ctx, f.fileClient) if err != nil { return serializer.NewError(serializer.CodeDBError, "Failed to start transaction", err) } for _, target := range targets { if err := fc.UpsertMetadata(ctx, target.Model, metadataMap, privateMap); err != nil { _ = inventory.Rollback(tx) return fmt.Errorf("failed to upsert metadata: %w", err) } if len(deleted) > 0 { if err := fc.RemoveMetadata(ctx, target.Model, deleted...); err != nil { _ = inventory.Rollback(tx) return fmt.Errorf("failed to remove metadata: %w", err) } } } if err := inventory.Commit(tx); err != nil { return serializer.NewError(serializer.CodeDBError, "Failed to commit metadata change", err) } return ae.Aggregate() }