feat(explorer): save user's view setting to server / optionally share view setting via share link (#2232)
This commit is contained in:
@@ -35,6 +35,7 @@ const (
|
||||
ContextHintTTL = 5 * 60 // 5 minutes
|
||||
|
||||
folderSummaryCachePrefix = "folder_summary_"
|
||||
defaultPageSize = 100
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -119,17 +120,46 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi
|
||||
searchParams := path.SearchParameters()
|
||||
isSearching := searchParams != nil
|
||||
|
||||
// Validate pagination args
|
||||
props := navigator.Capabilities(isSearching)
|
||||
if o.PageSize > props.MaxPageSize {
|
||||
o.PageSize = props.MaxPageSize
|
||||
}
|
||||
|
||||
parent, err := f.getFileByPath(ctx, navigator, path)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Parent not exist: %w", err)
|
||||
}
|
||||
|
||||
pageSize := 0
|
||||
orderDirection := ""
|
||||
orderBy := ""
|
||||
|
||||
view := navigator.GetView(ctx, parent)
|
||||
if view != nil {
|
||||
pageSize = view.PageSize
|
||||
orderDirection = view.OrderDirection
|
||||
orderBy = view.Order
|
||||
}
|
||||
|
||||
if o.PageSize > 0 {
|
||||
pageSize = o.PageSize
|
||||
}
|
||||
if o.OrderDirection != "" {
|
||||
orderDirection = o.OrderDirection
|
||||
}
|
||||
if o.OrderBy != "" {
|
||||
orderBy = o.OrderBy
|
||||
}
|
||||
|
||||
// Validate pagination args
|
||||
props := navigator.Capabilities(isSearching)
|
||||
if pageSize > props.MaxPageSize {
|
||||
pageSize = props.MaxPageSize
|
||||
} else if pageSize == 0 {
|
||||
pageSize = defaultPageSize
|
||||
}
|
||||
|
||||
if view != nil {
|
||||
view.PageSize = pageSize
|
||||
view.OrderDirection = orderDirection
|
||||
view.Order = orderBy
|
||||
}
|
||||
|
||||
var hintId *uuid.UUID
|
||||
if o.generateContextHint {
|
||||
newHintId := uuid.Must(uuid.NewV4())
|
||||
@@ -155,9 +185,9 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi
|
||||
children, err := navigator.Children(ctx, parent, &ListArgs{
|
||||
Page: &inventory.PaginationArgs{
|
||||
Page: o.FsOption.Page,
|
||||
PageSize: o.PageSize,
|
||||
OrderBy: o.OrderBy,
|
||||
Order: inventory.OrderDirection(o.OrderDirection),
|
||||
PageSize: pageSize,
|
||||
OrderBy: orderBy,
|
||||
Order: inventory.OrderDirection(orderDirection),
|
||||
UseCursorPagination: o.useCursorPagination,
|
||||
PageToken: o.pageToken,
|
||||
},
|
||||
@@ -188,6 +218,7 @@ func (f *DBFS) List(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fi
|
||||
SingleFileView: children.SingleFileView,
|
||||
Parent: parent,
|
||||
StoragePolicy: storagePolicy,
|
||||
View: view,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -270,89 +301,6 @@ func (f *DBFS) CreateEntity(ctx context.Context, file fs.File, policy *ent.Stora
|
||||
return fs.NewEntity(entity), 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()
|
||||
}
|
||||
|
||||
func (f *DBFS) SharedAddressTranslation(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.File, *fs.URI, error) {
|
||||
o := newDbfsOption()
|
||||
for _, opt := range opts {
|
||||
@@ -470,6 +418,9 @@ func (f *DBFS) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.Fil
|
||||
target.FileExtendedInfo = extendedInfo
|
||||
if target.OwnerID() == f.user.ID || f.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionIsAdmin)) {
|
||||
target.FileExtendedInfo.Shares = target.Model.Edges.Shares
|
||||
if target.Model.Props != nil {
|
||||
target.FileExtendedInfo.View = target.Model.Props.View
|
||||
}
|
||||
}
|
||||
|
||||
entities := target.Entities()
|
||||
|
||||
@@ -22,13 +22,20 @@ func init() {
|
||||
gob.Register(map[int]*File{})
|
||||
}
|
||||
|
||||
var filePool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &File{
|
||||
Children: make(map[string]*File),
|
||||
}
|
||||
},
|
||||
}
|
||||
var (
|
||||
filePool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &File{
|
||||
Children: make(map[string]*File),
|
||||
}
|
||||
},
|
||||
}
|
||||
defaultView = &types.ExplorerView{
|
||||
PageSize: defaultPageSize,
|
||||
View: "grid",
|
||||
Thumbnail: true,
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
File struct {
|
||||
@@ -42,7 +49,8 @@ type (
|
||||
FileExtendedInfo *fs.FileExtendedInfo
|
||||
FileFolderSummary *fs.FolderSummary
|
||||
|
||||
mu *sync.Mutex
|
||||
disableView bool
|
||||
mu *sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
@@ -181,6 +189,31 @@ func (f *File) Uri(isRoot bool) *fs.URI {
|
||||
return parent.Path[index].Join(elements...)
|
||||
}
|
||||
|
||||
// View returns the view setting of the file, can be inherited from parent.
|
||||
func (f *File) View() *types.ExplorerView {
|
||||
// If owner has disabled view sync, return nil
|
||||
owner := f.Owner()
|
||||
if owner != nil && owner.Settings != nil && owner.Settings.DisableViewSync {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If navigator has disabled view sync, return nil
|
||||
userRoot := f.UserRoot()
|
||||
if userRoot == nil || userRoot.disableView {
|
||||
return nil
|
||||
}
|
||||
|
||||
current := f
|
||||
for current != nil {
|
||||
if current.Model.Props != nil && current.Model.Props.View != nil {
|
||||
return current.Model.Props.View
|
||||
}
|
||||
current = current.Parent
|
||||
}
|
||||
|
||||
return defaultView
|
||||
}
|
||||
|
||||
// UserRoot return the root file from user's view.
|
||||
func (f *File) UserRoot() *File {
|
||||
root := f
|
||||
|
||||
@@ -106,6 +106,7 @@ func (n *myNavigator) To(ctx context.Context, path *fs.URI) (*File, error) {
|
||||
rootPath := path.Root()
|
||||
n.root.Path[pathIndexRoot], n.root.Path[pathIndexUser] = rootPath, rootPath
|
||||
n.root.OwnerModel = targetUser
|
||||
n.root.disableView = fsUid != n.user.ID
|
||||
n.root.IsUserRoot = true
|
||||
n.root.CapabilitiesBs = n.Capabilities(false).Capability
|
||||
}
|
||||
@@ -178,3 +179,7 @@ func (n *myNavigator) FollowTx(ctx context.Context) (func(), error) {
|
||||
func (n *myNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *myNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView {
|
||||
return file.View()
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ type (
|
||||
FollowTx(ctx context.Context) (func(), error)
|
||||
// ExecuteHook performs custom operations before or after certain actions.
|
||||
ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error
|
||||
// GetView returns the view setting of the given file.
|
||||
GetView(ctx context.Context, file *File) *types.ExplorerView
|
||||
}
|
||||
|
||||
State interface{}
|
||||
@@ -100,6 +102,7 @@ const (
|
||||
NavigatorCapability_CommunityPlacehodler8
|
||||
NavigatorCapability_CommunityPlacehodler9
|
||||
NavigatorCapabilityEnterFolder
|
||||
NavigatorCapabilityModifyProps
|
||||
|
||||
searchTokenSeparator = "|"
|
||||
)
|
||||
@@ -120,6 +123,7 @@ func init() {
|
||||
NavigatorCapabilityInfo: true,
|
||||
NavigatorCapabilityVersionControl: true,
|
||||
NavigatorCapabilityEnterFolder: true,
|
||||
NavigatorCapabilityModifyProps: true,
|
||||
}, myNavigatorCapability)
|
||||
boolset.Sets(map[NavigatorCapability]bool{
|
||||
NavigatorCapabilityDownloadFile: true,
|
||||
@@ -129,6 +133,7 @@ func init() {
|
||||
NavigatorCapabilityInfo: true,
|
||||
NavigatorCapabilityVersionControl: true,
|
||||
NavigatorCapabilityEnterFolder: true,
|
||||
NavigatorCapabilityModifyProps: true,
|
||||
}, shareNavigatorCapability)
|
||||
boolset.Sets(map[NavigatorCapability]bool{
|
||||
NavigatorCapabilityListChildren: true,
|
||||
|
||||
138
pkg/filemanager/fs/dbfs/props.go
Normal file
138
pkg/filemanager/fs/dbfs/props.go
Normal file
@@ -0,0 +1,138 @@
|
||||
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, false, 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()
|
||||
}
|
||||
@@ -148,6 +148,7 @@ func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error)
|
||||
n.shareRoot.Path[pathIndexUser] = path.Root()
|
||||
n.shareRoot.OwnerModel = n.owner
|
||||
n.shareRoot.IsUserRoot = true
|
||||
n.shareRoot.disableView = (share.Props == nil || !share.Props.ShareView) && n.user.ID != n.owner.ID
|
||||
n.shareRoot.CapabilitiesBs = n.Capabilities(false).Capability
|
||||
|
||||
// Check if any ancestors is deleted
|
||||
@@ -303,3 +304,7 @@ func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType,
|
||||
func (n *shareNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth int, f WalkFunc) error {
|
||||
return n.baseNavigator.walk(ctx, levelFiles, limit, depth, f)
|
||||
}
|
||||
|
||||
func (n *shareNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView {
|
||||
return file.View()
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
||||
@@ -139,3 +142,10 @@ func (n *sharedWithMeNavigator) FollowTx(ctx context.Context) (func(), error) {
|
||||
func (n *sharedWithMeNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *sharedWithMeNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView {
|
||||
if view, ok := n.user.Settings.FsViewMap[string(constants.FileSystemSharedWithMe)]; ok {
|
||||
return &view
|
||||
}
|
||||
return defaultView
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ package dbfs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/cache"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs"
|
||||
@@ -13,7 +16,26 @@ import (
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
)
|
||||
|
||||
var trashNavigatorCapability = &boolset.BooleanSet{}
|
||||
var (
|
||||
trashNavigatorCapability = &boolset.BooleanSet{}
|
||||
defaultTrashView = &types.ExplorerView{
|
||||
View: "list",
|
||||
Columns: []types.ListViewColumn{
|
||||
{
|
||||
Type: 0,
|
||||
},
|
||||
{
|
||||
Type: 2,
|
||||
},
|
||||
{
|
||||
Type: 8,
|
||||
},
|
||||
{
|
||||
Type: 7,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// NewTrashNavigator creates a navigator for user's "trash" file system.
|
||||
func NewTrashNavigator(u *ent.User, fileClient inventory.FileClient, l logging.Logger, config *setting.DBFS,
|
||||
@@ -135,3 +157,10 @@ func (n *trashNavigator) FollowTx(ctx context.Context) (func(), error) {
|
||||
func (n *trashNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *trashNavigator) GetView(ctx context.Context, file *File) *types.ExplorerView {
|
||||
if view, ok := n.user.Settings.FsViewMap[string(constants.FileSystemTrash)]; ok {
|
||||
return &view
|
||||
}
|
||||
return defaultTrashView
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@ type (
|
||||
GetFileFromDirectLink(ctx context.Context, dl *ent.DirectLink) (File, error)
|
||||
// TraverseFile traverses a file to its root file, return the file with linked root.
|
||||
TraverseFile(ctx context.Context, fileID int) (File, error)
|
||||
// PatchProps patches the props of a file.
|
||||
PatchProps(ctx context.Context, uri *URI, props *types.FileProps, delete bool) error
|
||||
}
|
||||
|
||||
UploadManager interface {
|
||||
@@ -165,6 +167,7 @@ type (
|
||||
FolderSummary() *FolderSummary
|
||||
Capabilities() *boolset.BooleanSet
|
||||
IsRootFolder() bool
|
||||
View() *types.ExplorerView
|
||||
}
|
||||
|
||||
Entities []Entity
|
||||
@@ -187,6 +190,7 @@ type (
|
||||
StorageUsed int64
|
||||
Shares []*ent.Share
|
||||
EntityStoragePolicies map[int]*ent.StoragePolicy
|
||||
View *types.ExplorerView
|
||||
}
|
||||
|
||||
FolderSummary struct {
|
||||
@@ -215,6 +219,7 @@ type (
|
||||
MixedType bool
|
||||
SingleFileView bool
|
||||
StoragePolicy *ent.StoragePolicy
|
||||
View *types.ExplorerView
|
||||
}
|
||||
|
||||
// NavigatorProps is the properties of current filesystem.
|
||||
|
||||
@@ -75,6 +75,8 @@ type (
|
||||
CastStoragePolicyOnSlave(ctx context.Context, policy *ent.StoragePolicy) *ent.StoragePolicy
|
||||
// GetStorageDriver gets storage driver for given policy
|
||||
GetStorageDriver(ctx context.Context, policy *ent.StoragePolicy) (driver.Handler, error)
|
||||
// PatchView patches the view setting of a file
|
||||
PatchView(ctx context.Context, uri *fs.URI, view *types.ExplorerView) error
|
||||
}
|
||||
|
||||
ShareManagement interface {
|
||||
@@ -111,6 +113,7 @@ type (
|
||||
IsPrivate bool
|
||||
RemainDownloads int
|
||||
Expire *time.Time
|
||||
ShareView bool
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/application/constants"
|
||||
"github.com/cloudreve/Cloudreve/v4/ent"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory"
|
||||
"github.com/cloudreve/Cloudreve/v4/inventory/types"
|
||||
@@ -261,6 +262,10 @@ func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *C
|
||||
password = util.RandString(8, util.RandomLowerCases)
|
||||
}
|
||||
|
||||
props := &types.ShareProps{
|
||||
ShareView: args.ShareView,
|
||||
}
|
||||
|
||||
share, err := shareClient.Upsert(ctx, &inventory.CreateShareParams{
|
||||
OwnerID: file.OwnerID(),
|
||||
FileID: file.ID(),
|
||||
@@ -268,6 +273,7 @@ func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *C
|
||||
Expires: args.Expire,
|
||||
RemainDownloads: args.RemainDownloads,
|
||||
Existed: existed,
|
||||
Props: props,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -281,6 +287,39 @@ func (m *manager) TraverseFile(ctx context.Context, fileID int) (fs.File, error)
|
||||
return m.fs.TraverseFile(ctx, fileID)
|
||||
}
|
||||
|
||||
func (m *manager) PatchView(ctx context.Context, uri *fs.URI, view *types.ExplorerView) error {
|
||||
if uri.PathTrimmed() == "" && uri.FileSystem() != constants.FileSystemMy && uri.FileSystem() != constants.FileSystemShare {
|
||||
if m.user.Settings.FsViewMap == nil {
|
||||
m.user.Settings.FsViewMap = make(map[string]types.ExplorerView)
|
||||
}
|
||||
|
||||
if view == nil {
|
||||
delete(m.user.Settings.FsViewMap, string(uri.FileSystem()))
|
||||
} else {
|
||||
m.user.Settings.FsViewMap[string(uri.FileSystem())] = *view
|
||||
}
|
||||
|
||||
if err := m.dep.UserClient().SaveSettings(ctx, m.user); err != nil {
|
||||
return serializer.NewError(serializer.CodeDBError, "failed to save user settings", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
patch := &types.FileProps{
|
||||
View: view,
|
||||
}
|
||||
isDelete := view == nil
|
||||
if isDelete {
|
||||
patch.View = &types.ExplorerView{}
|
||||
}
|
||||
if err := m.fs.PatchProps(ctx, uri, patch, isDelete); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEntityDisplayName(f fs.File, e fs.Entity) string {
|
||||
switch e.Type() {
|
||||
case types.EntityTypeThumbnail:
|
||||
|
||||
Reference in New Issue
Block a user