Init V4 community edition (#2265)
* Init V4 community edition * Init V4 community edition
This commit is contained in:
296
pkg/filemanager/manager/operation.go
Normal file
296
pkg/filemanager/manager/operation.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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/filemanager/fs"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs/dbfs"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/filemanager/lock"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/serializer"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/setting"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/util"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const (
|
||||
EntityUrlCacheKeyPrefix = "entity_url_"
|
||||
DownloadSentinelCachePrefix = "download_sentinel_"
|
||||
)
|
||||
|
||||
type (
|
||||
ListArgs struct {
|
||||
Page int
|
||||
PageSize int
|
||||
PageToken string
|
||||
Order string
|
||||
OrderDirection string
|
||||
// StreamResponseCallback is used for streamed list operation, e.g. searching files.
|
||||
// Whenever a new item is found, this callback will be called with the current item and the parent item.
|
||||
StreamResponseCallback func(fs.File, []fs.File)
|
||||
}
|
||||
|
||||
EntityUrlCache struct {
|
||||
Url string
|
||||
ExpireAt *time.Time
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(EntityUrlCache{})
|
||||
}
|
||||
|
||||
func (m *manager) Get(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.File, error) {
|
||||
return m.fs.Get(ctx, path, opts...)
|
||||
}
|
||||
|
||||
func (m *manager) List(ctx context.Context, path *fs.URI, args *ListArgs) (fs.File, *fs.ListFileResult, error) {
|
||||
dbfsSetting := m.settings.DBFS(ctx)
|
||||
opts := []fs.Option{
|
||||
fs.WithPageSize(args.PageSize),
|
||||
fs.WithOrderBy(args.Order),
|
||||
fs.WithOrderDirection(args.OrderDirection),
|
||||
dbfs.WithFilePublicMetadata(),
|
||||
dbfs.WithContextHint(),
|
||||
dbfs.WithFileShareIfOwned(),
|
||||
}
|
||||
|
||||
searchParams := path.SearchParameters()
|
||||
if searchParams != nil {
|
||||
if dbfsSetting.UseSSEForSearch {
|
||||
opts = append(opts, dbfs.WithStreamListResponseCallback(args.StreamResponseCallback))
|
||||
}
|
||||
|
||||
if searchParams.Category != "" {
|
||||
// Overwrite search query with predefined category
|
||||
category := fs.SearchCategoryFromString(searchParams.Category)
|
||||
if category == setting.CategoryUnknown {
|
||||
return nil, nil, fmt.Errorf("unknown category: %s", searchParams.Category)
|
||||
}
|
||||
|
||||
path = path.SetQuery(m.settings.SearchCategoryQuery(ctx, category))
|
||||
searchParams = path.SearchParameters()
|
||||
}
|
||||
}
|
||||
|
||||
if dbfsSetting.UseCursorPagination || searchParams != nil {
|
||||
opts = append(opts, dbfs.WithCursorPagination(args.PageToken))
|
||||
} else {
|
||||
opts = append(opts, fs.WithPage(args.Page))
|
||||
}
|
||||
|
||||
return m.fs.List(ctx, path, opts...)
|
||||
}
|
||||
|
||||
func (m *manager) SharedAddressTranslation(ctx context.Context, path *fs.URI, opts ...fs.Option) (fs.File, *fs.URI, error) {
|
||||
o := newOption()
|
||||
for _, opt := range opts {
|
||||
opt.Apply(o)
|
||||
}
|
||||
|
||||
return m.fs.SharedAddressTranslation(ctx, path)
|
||||
}
|
||||
|
||||
func (m *manager) Create(ctx context.Context, path *fs.URI, fileType types.FileType, opts ...fs.Option) (fs.File, error) {
|
||||
o := newOption()
|
||||
for _, opt := range opts {
|
||||
opt.Apply(o)
|
||||
}
|
||||
|
||||
if m.stateless {
|
||||
return nil, o.Node.CreateFile(ctx, &fs.StatelessCreateFileService{
|
||||
Path: path.String(),
|
||||
Type: fileType,
|
||||
UserID: o.StatelessUserID,
|
||||
})
|
||||
}
|
||||
|
||||
isSymbolic := false
|
||||
if o.Metadata != nil {
|
||||
if err := m.validateMetadata(ctx, lo.MapToSlice(o.Metadata, func(key string, value string) fs.MetadataPatch {
|
||||
if key == shareRedirectMetadataKey {
|
||||
isSymbolic = true
|
||||
}
|
||||
|
||||
return fs.MetadataPatch{
|
||||
Key: key,
|
||||
Value: value,
|
||||
}
|
||||
})...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if isSymbolic {
|
||||
opts = append(opts, dbfs.WithSymbolicLink())
|
||||
}
|
||||
|
||||
return m.fs.Create(ctx, path, fileType, opts...)
|
||||
}
|
||||
|
||||
func (m *manager) Rename(ctx context.Context, path *fs.URI, newName string) (fs.File, error) {
|
||||
return m.fs.Rename(ctx, path, newName)
|
||||
}
|
||||
|
||||
func (m *manager) MoveOrCopy(ctx context.Context, src []*fs.URI, dst *fs.URI, isCopy bool) error {
|
||||
return m.fs.MoveOrCopy(ctx, src, dst, isCopy)
|
||||
}
|
||||
|
||||
func (m *manager) SoftDelete(ctx context.Context, path ...*fs.URI) error {
|
||||
return m.fs.SoftDelete(ctx, path...)
|
||||
}
|
||||
|
||||
func (m *manager) Delete(ctx context.Context, path []*fs.URI, opts ...fs.Option) error {
|
||||
o := newOption()
|
||||
for _, opt := range opts {
|
||||
opt.Apply(o)
|
||||
}
|
||||
|
||||
if !o.SkipSoftDelete && !o.SysSkipSoftDelete {
|
||||
return m.SoftDelete(ctx, path...)
|
||||
}
|
||||
|
||||
staleEntities, err := m.fs.Delete(ctx, path, fs.WithUnlinkOnly(o.UnlinkOnly), fs.WithSysSkipSoftDelete(o.SysSkipSoftDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.l.Debug("New stale entities: %v", staleEntities)
|
||||
|
||||
// Delete stale entities
|
||||
if len(staleEntities) > 0 {
|
||||
t, err := newExplicitEntityRecycleTask(ctx, lo.Map(staleEntities, func(entity fs.Entity, index int) int {
|
||||
return entity.ID()
|
||||
}))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create explicit entity recycle task: %w", err)
|
||||
}
|
||||
|
||||
if err := m.dep.EntityRecycleQueue(ctx).QueueTask(ctx, t); err != nil {
|
||||
return fmt.Errorf("failed to queue explicit entity recycle task: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) Walk(ctx context.Context, path *fs.URI, depth int, f fs.WalkFunc, opts ...fs.Option) error {
|
||||
return m.fs.Walk(ctx, path, depth, f, opts...)
|
||||
}
|
||||
|
||||
func (m *manager) Capacity(ctx context.Context) (*fs.Capacity, error) {
|
||||
res, err := m.fs.Capacity(ctx, m.user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (m *manager) CheckIfCapacityExceeded(ctx context.Context) error {
|
||||
ctx = context.WithValue(ctx, inventory.LoadUserGroup{}, true)
|
||||
capacity, err := m.Capacity(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user capacity: %w", err)
|
||||
}
|
||||
|
||||
if capacity.Used <= capacity.Total {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *manager) ConfirmLock(ctx context.Context, ancestor fs.File, uri *fs.URI, token ...string) (func(), fs.LockSession, error) {
|
||||
return l.fs.ConfirmLock(ctx, ancestor, uri, token...)
|
||||
}
|
||||
|
||||
func (l *manager) Lock(ctx context.Context, d time.Duration, requester *ent.User, zeroDepth bool,
|
||||
application lock.Application, uri *fs.URI, token string) (fs.LockSession, error) {
|
||||
return l.fs.Lock(ctx, d, requester, zeroDepth, application, uri, token)
|
||||
}
|
||||
|
||||
func (l *manager) Unlock(ctx context.Context, tokens ...string) error {
|
||||
return l.fs.Unlock(ctx, tokens...)
|
||||
}
|
||||
|
||||
func (l *manager) Refresh(ctx context.Context, d time.Duration, token string) (lock.LockDetails, error) {
|
||||
return l.fs.Refresh(ctx, d, token)
|
||||
}
|
||||
|
||||
func (l *manager) Restore(ctx context.Context, path ...*fs.URI) error {
|
||||
return l.fs.Restore(ctx, path...)
|
||||
}
|
||||
|
||||
func (l *manager) CreateOrUpdateShare(ctx context.Context, path *fs.URI, args *CreateShareArgs) (*ent.Share, error) {
|
||||
file, err := l.fs.Get(ctx, path, dbfs.WithRequiredCapabilities(dbfs.NavigatorCapabilityShare))
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeNotFound, "src file not found", err)
|
||||
}
|
||||
|
||||
// Only file owner can share file
|
||||
if file.OwnerID() != l.user.ID {
|
||||
return nil, serializer.NewError(serializer.CodeNoPermissionErr, "permission denied", nil)
|
||||
}
|
||||
|
||||
if file.IsSymbolic() {
|
||||
return nil, serializer.NewError(serializer.CodeNoPermissionErr, "cannot share symbolic file", nil)
|
||||
}
|
||||
|
||||
var existed *ent.Share
|
||||
shareClient := l.dep.ShareClient()
|
||||
if args.ExistedShareID != 0 {
|
||||
loadShareCtx := context.WithValue(ctx, inventory.LoadShareFile{}, true)
|
||||
existed, err = shareClient.GetByID(loadShareCtx, args.ExistedShareID)
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeNotFound, "failed to get existed share", err)
|
||||
}
|
||||
|
||||
if existed.Edges.File.ID != file.ID() {
|
||||
return nil, serializer.NewError(serializer.CodeNotFound, "share link not found", nil)
|
||||
}
|
||||
}
|
||||
|
||||
password := ""
|
||||
if args.IsPrivate {
|
||||
password = util.RandString(8, util.RandomLowerCases)
|
||||
}
|
||||
|
||||
share, err := shareClient.Upsert(ctx, &inventory.CreateShareParams{
|
||||
OwnerID: file.OwnerID(),
|
||||
FileID: file.ID(),
|
||||
Password: password,
|
||||
Expires: args.Expire,
|
||||
RemainDownloads: args.RemainDownloads,
|
||||
Existed: existed,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, serializer.NewError(serializer.CodeDBError, "failed to create share", err)
|
||||
}
|
||||
|
||||
return share, nil
|
||||
}
|
||||
|
||||
func getEntityDisplayName(f fs.File, e fs.Entity) string {
|
||||
switch e.Type() {
|
||||
case types.EntityTypeThumbnail:
|
||||
return fmt.Sprintf("%s_thumbnail", f.DisplayName())
|
||||
case types.EntityTypeLivePhoto:
|
||||
return fmt.Sprintf("%s_live_photo.mov", f.DisplayName())
|
||||
default:
|
||||
return f.Name()
|
||||
}
|
||||
}
|
||||
|
||||
func expireTimeToTTL(expireAt *time.Time) int {
|
||||
if expireAt == nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return int(time.Until(*expireAt).Seconds())
|
||||
}
|
||||
Reference in New Issue
Block a user