Cloudreve/pkg/filemanager/fs/dbfs/share_navigator.go

311 lines
8.2 KiB
Go

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.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
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
}
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()
}