Init V4 community edition (#2265)
* Init V4 community edition * Init V4 community edition
This commit is contained in:
324
pkg/filemanager/fs/dbfs/share_navigator.go
Normal file
324
pkg/filemanager/fs/dbfs/share_navigator.go
Normal file
@@ -0,0 +1,324 @@
|
||||
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"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrShareNotFound = serializer.NewError(serializer.CodeNotFound, "Shared file does not exist", nil)
|
||||
ErrNotPurchased = serializer.NewError(serializer.CodePurchaseRequired, "You need to purchased this share", nil)
|
||||
)
|
||||
|
||||
const (
|
||||
PurchaseTicketHeader = constants.CrHeaderPrefix + "Purchase-Ticket"
|
||||
)
|
||||
|
||||
var shareNavigatorCapability = &boolset.BooleanSet{}
|
||||
|
||||
// NewShareNavigator creates a navigator for user's "shared" file system.
|
||||
func NewShareNavigator(u *ent.User, fileClient inventory.FileClient, shareClient inventory.ShareClient,
|
||||
l logging.Logger, config *setting.DBFS, hasher hashid.Encoder) Navigator {
|
||||
n := &shareNavigator{
|
||||
user: u,
|
||||
l: l,
|
||||
fileClient: fileClient,
|
||||
shareClient: shareClient,
|
||||
config: config,
|
||||
}
|
||||
n.baseNavigator = newBaseNavigator(fileClient, defaultFilter, u, hasher, config)
|
||||
return n
|
||||
}
|
||||
|
||||
type (
|
||||
shareNavigator struct {
|
||||
l logging.Logger
|
||||
user *ent.User
|
||||
fileClient inventory.FileClient
|
||||
shareClient inventory.ShareClient
|
||||
config *setting.DBFS
|
||||
|
||||
*baseNavigator
|
||||
shareRoot *File
|
||||
singleFileShare bool
|
||||
ownerRoot *File
|
||||
share *ent.Share
|
||||
owner *ent.User
|
||||
disableRecycle bool
|
||||
persist func()
|
||||
}
|
||||
|
||||
shareNavigatorState struct {
|
||||
ShareRoot *File
|
||||
OwnerRoot *File
|
||||
SingleFileShare bool
|
||||
Share *ent.Share
|
||||
Owner *ent.User
|
||||
}
|
||||
)
|
||||
|
||||
func (n *shareNavigator) PersistState(kv cache.Driver, key string) {
|
||||
n.disableRecycle = true
|
||||
n.persist = func() {
|
||||
kv.Set(key, shareNavigatorState{
|
||||
ShareRoot: n.shareRoot,
|
||||
OwnerRoot: n.ownerRoot,
|
||||
SingleFileShare: n.singleFileShare,
|
||||
Share: n.share,
|
||||
Owner: n.owner,
|
||||
}, ContextHintTTL)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *shareNavigator) RestoreState(s State) error {
|
||||
n.disableRecycle = true
|
||||
if state, ok := s.(shareNavigatorState); ok {
|
||||
n.shareRoot = state.ShareRoot
|
||||
n.ownerRoot = state.OwnerRoot
|
||||
n.singleFileShare = state.SingleFileShare
|
||||
n.share = state.Share
|
||||
n.owner = state.Owner
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid state type: %T", s)
|
||||
}
|
||||
|
||||
func (n *shareNavigator) Recycle() {
|
||||
if n.persist != nil {
|
||||
n.persist()
|
||||
n.persist = nil
|
||||
}
|
||||
|
||||
if !n.disableRecycle {
|
||||
if n.ownerRoot != nil {
|
||||
n.ownerRoot.Recycle()
|
||||
} else if n.shareRoot != nil {
|
||||
n.shareRoot.Recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *shareNavigator) Root(ctx context.Context, path *fs.URI) (*File, error) {
|
||||
ctx = context.WithValue(ctx, inventory.LoadShareUser{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadUserGroup{}, true)
|
||||
ctx = context.WithValue(ctx, inventory.LoadShareFile{}, true)
|
||||
share, err := n.shareClient.GetByHashID(ctx, path.ID(hashid.EncodeUserID(n.hasher, n.user.ID)))
|
||||
if err != nil {
|
||||
return nil, ErrShareNotFound.WithError(err)
|
||||
}
|
||||
|
||||
if err := inventory.IsValidShare(share); err != nil {
|
||||
return nil, ErrShareNotFound.WithError(err)
|
||||
}
|
||||
|
||||
n.owner = share.Edges.User
|
||||
|
||||
// Check password
|
||||
if share.Password != "" && share.Password != path.Password() {
|
||||
return nil, ErrShareIncorrectPassword
|
||||
}
|
||||
|
||||
// Share permission setting should overwrite root folder's permission
|
||||
n.shareRoot = newFile(nil, share.Edges.File)
|
||||
|
||||
// Find the user side root of the file.
|
||||
ownerRoot, err := n.findRoot(ctx, n.shareRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if n.shareRoot.Type() == types.FileTypeFile {
|
||||
n.singleFileShare = true
|
||||
n.shareRoot = n.shareRoot.Parent
|
||||
}
|
||||
|
||||
n.shareRoot.Path[pathIndexUser] = path.Root()
|
||||
n.shareRoot.OwnerModel = n.owner
|
||||
n.shareRoot.IsUserRoot = true
|
||||
n.shareRoot.CapabilitiesBs = n.Capabilities(false).Capability
|
||||
|
||||
// Check if any ancestors is deleted
|
||||
if ownerRoot.Name() != inventory.RootFolderName {
|
||||
return nil, ErrShareNotFound
|
||||
}
|
||||
|
||||
if n.user.ID != n.owner.ID && !n.user.Edges.Group.Permissions.Enabled(int(types.GroupPermissionShareDownload)) {
|
||||
return nil, serializer.NewError(
|
||||
serializer.CodeNoPermissionErr,
|
||||
fmt.Sprintf("You don't have permission to access share links"),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
n.ownerRoot = ownerRoot
|
||||
n.ownerRoot.Path[pathIndexRoot] = newMyIDUri(hashid.EncodeUserID(n.hasher, n.owner.ID))
|
||||
n.share = share
|
||||
return n.shareRoot, nil
|
||||
}
|
||||
|
||||
func (n *shareNavigator) To(ctx context.Context, path *fs.URI) (*File, error) {
|
||||
if n.shareRoot == nil {
|
||||
root, err := n.Root(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n.shareRoot = root
|
||||
}
|
||||
|
||||
current, lastAncestor := n.shareRoot, n.shareRoot
|
||||
elements := path.Elements()
|
||||
|
||||
// If target is root of single file share, the root itself is the target.
|
||||
if len(elements) <= 1 && n.singleFileShare {
|
||||
file, err := n.latestSharedSingleFile(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(elements) == 1 && file.Name() != elements[0] {
|
||||
return nil, fs.ErrPathNotExist
|
||||
}
|
||||
|
||||
return file, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
for index, element := range elements {
|
||||
lastAncestor = current
|
||||
current, err = n.walkNext(ctx, current, element, index == len(elements)-1)
|
||||
if err != nil {
|
||||
return lastAncestor, fmt.Errorf("failed to walk into %q: %w", element, err)
|
||||
}
|
||||
}
|
||||
|
||||
return current, nil
|
||||
}
|
||||
|
||||
func (n *shareNavigator) walkNext(ctx context.Context, root *File, next string, isLeaf bool) (*File, error) {
|
||||
nextFile, err := n.baseNavigator.walkNext(ctx, root, next, isLeaf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nextFile, nil
|
||||
}
|
||||
|
||||
func (n *shareNavigator) Children(ctx context.Context, parent *File, args *ListArgs) (*ListResult, error) {
|
||||
if n.singleFileShare {
|
||||
file, err := n.latestSharedSingleFile(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ListResult{
|
||||
Files: []*File{file},
|
||||
Pagination: &inventory.PaginationResults{},
|
||||
SingleFileView: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return n.baseNavigator.children(ctx, parent, args)
|
||||
}
|
||||
|
||||
func (n *shareNavigator) latestSharedSingleFile(ctx context.Context) (*File, error) {
|
||||
if n.singleFileShare {
|
||||
file, err := n.fileClient.GetByID(ctx, n.share.Edges.File.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := newFile(n.shareRoot, file)
|
||||
f.OwnerModel = n.shareRoot.OwnerModel
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
return nil, fs.ErrPathNotExist
|
||||
}
|
||||
|
||||
func (n *shareNavigator) Capabilities(isSearching bool) *fs.NavigatorProps {
|
||||
res := &fs.NavigatorProps{
|
||||
Capability: shareNavigatorCapability,
|
||||
OrderDirectionOptions: fullOrderDirectionOption,
|
||||
OrderByOptions: fullOrderByOption,
|
||||
MaxPageSize: n.config.MaxPageSize,
|
||||
}
|
||||
|
||||
if isSearching {
|
||||
res.OrderByOptions = nil
|
||||
res.OrderDirectionOptions = nil
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (n *shareNavigator) FollowTx(ctx context.Context) (func(), error) {
|
||||
if _, ok := ctx.Value(inventory.TxCtx{}).(*inventory.Tx); !ok {
|
||||
return nil, fmt.Errorf("navigator: no inherited transaction found in context")
|
||||
}
|
||||
newFileClient, _, _, err := inventory.WithTx(ctx, n.fileClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newSharClient, _, _, err := inventory.WithTx(ctx, n.shareClient)
|
||||
|
||||
oldFileClient, oldShareClient := n.fileClient, n.shareClient
|
||||
revert := func() {
|
||||
n.fileClient = oldFileClient
|
||||
n.shareClient = oldShareClient
|
||||
n.baseNavigator.fileClient = oldFileClient
|
||||
}
|
||||
|
||||
n.fileClient = newFileClient
|
||||
n.shareClient = newSharClient
|
||||
n.baseNavigator.fileClient = newFileClient
|
||||
return revert, nil
|
||||
}
|
||||
|
||||
func (n *shareNavigator) ExecuteHook(ctx context.Context, hookType fs.HookType, file *File) error {
|
||||
switch hookType {
|
||||
case fs.HookTypeBeforeDownload:
|
||||
if n.singleFileShare {
|
||||
return n.shareClient.Downloaded(ctx, n.share)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findRoot finds the root folder of the given child.
|
||||
func (n *shareNavigator) findRoot(ctx context.Context, child *File) (*File, error) {
|
||||
root := child
|
||||
for {
|
||||
newRoot, err := n.baseNavigator.walkUp(ctx, root)
|
||||
if err != nil {
|
||||
if !ent.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
root = newRoot
|
||||
}
|
||||
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func (n *shareNavigator) Walk(ctx context.Context, levelFiles []*File, limit, depth int, f WalkFunc) error {
|
||||
return n.baseNavigator.walk(ctx, levelFiles, limit, depth, f)
|
||||
}
|
||||
Reference in New Issue
Block a user