package dbfs import ( "encoding/gob" "path" "sync" "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/boolset" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/fs" "github.com/cloudreve/Cloudreve/v4/pkg/util" "github.com/samber/lo" ) func init() { gob.Register(File{}) gob.Register(shareNavigatorState{}) gob.Register(map[string]*File{}) gob.Register(map[int]*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 { Model *ent.File Children map[string]*File Parent *File Path [2]*fs.URI OwnerModel *ent.User IsUserRoot bool CapabilitiesBs *boolset.BooleanSet FileExtendedInfo *fs.FileExtendedInfo FileFolderSummary *fs.FolderSummary disableView bool mu *sync.Mutex } ) const ( MetadataSysPrefix = "sys:" MetadataUploadSessionPrefix = MetadataSysPrefix + "upload_session" MetadataUploadSessionID = MetadataUploadSessionPrefix + "_id" MetadataSharedRedirect = MetadataSysPrefix + "shared_redirect" MetadataRestoreUri = MetadataSysPrefix + "restore_uri" MetadataExpectedCollectTime = MetadataSysPrefix + "expected_collect_time" MetadataSharedOwner = MetadataSysPrefix + "shared_owner" ThumbMetadataPrefix = "thumb:" ThumbDisabledKey = ThumbMetadataPrefix + "disabled" pathIndexRoot = 0 pathIndexUser = 1 ) func (f *File) Name() string { return f.Model.Name } func (f *File) IsNil() bool { return f == nil } func (f *File) DisplayName() string { if uri, ok := f.Metadata()[MetadataRestoreUri]; ok { restoreUri, err := fs.NewUriFromString(uri) if err != nil { return f.Name() } return path.Base(restoreUri.Path()) } return f.Name() } func (f *File) CanHaveChildren() bool { return f.Type() == types.FileTypeFolder && !f.IsSymbolic() } func (f *File) Ext() string { return util.Ext(f.Name()) } func (f *File) ID() int { return f.Model.ID } func (f *File) IsSymbolic() bool { return f.Model.IsSymbolic } func (f *File) Type() types.FileType { return types.FileType(f.Model.Type) } func (f *File) Size() int64 { return f.Model.Size } func (f *File) SizeUsed() int64 { return lo.SumBy(f.Entities(), func(item fs.Entity) int64 { return item.Size() }) } func (f *File) UpdatedAt() time.Time { return f.Model.UpdatedAt } func (f *File) CreatedAt() time.Time { return f.Model.CreatedAt } func (f *File) ExtendedInfo() *fs.FileExtendedInfo { return f.FileExtendedInfo } func (f *File) Owner() *ent.User { parent := f for parent != nil { if parent.OwnerModel != nil { return parent.OwnerModel } parent = parent.Parent } return nil } func (f *File) OwnerID() int { return f.Model.OwnerID } func (f *File) Shared() bool { return len(f.Model.Edges.Shares) > 0 } func (f *File) Metadata() map[string]string { if f.Model.Edges.Metadata == nil { return nil } return lo.Associate(f.Model.Edges.Metadata, func(item *ent.Metadata) (string, string) { return item.Name, item.Value }) } // Uri returns the URI of the file. // If isRoot is true, the URI will be returned from owner's view. // Otherwise, the URI will be returned from user's view. func (f *File) Uri(isRoot bool) *fs.URI { index := 1 if isRoot { index = 0 } if f.Path[index] != nil || f.Parent == nil { return f.Path[index] } // Find the root file elements := make([]string, 0) parent := f for parent.Parent != nil && parent.Path[index] == nil { elements = append([]string{parent.Name()}, elements...) parent = parent.Parent } if parent.Path[index] == nil { return nil } 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 for root != nil && !root.IsUserRoot { root = root.Parent } return root } // Root return the root file from owner's view. func (f *File) Root() *File { root := f for root.Parent != nil { root = root.Parent } return root } // RootUri return the URI of the user root file under owner's view. func (f *File) RootUri() *fs.URI { return f.UserRoot().Uri(true) } func (f *File) Replace(model *ent.File) *File { f.mu.Lock() delete(f.Parent.Children, f.Model.Name) f.mu.Unlock() defer f.Recycle() replaced := newFile(f.Parent, model) if f.IsRootFile() { // If target is a root file, the user path should remain the same. replaced.Path[pathIndexUser] = f.Path[pathIndexUser] } return replaced } // Ancestors return all ancestors of the file, until the owner root is reached. func (f *File) Ancestors() []*File { return f.AncestorsChain()[1:] } // AncestorsChain return all ancestors of the file (including itself), until the owner root is reached. func (f *File) AncestorsChain() []*File { ancestors := make([]*File, 0) parent := f for parent != nil { ancestors = append(ancestors, parent) parent = parent.Parent } return ancestors } func (f *File) PolicyID() int { root := f return root.Model.StoragePolicyFiles } // IsRootFolder return true if the file is the root folder under user's view. func (f *File) IsRootFolder() bool { return f.Type() == types.FileTypeFolder && f.IsRootFile() } // IsRootFile return true if the file is the root file under user's view. func (f *File) IsRootFile() bool { uri := f.Uri(false) p := uri.Path() return f.Model.Name == inventory.RootFolderName || p == fs.Separator || p == "" } func (f *File) Entities() []fs.Entity { return lo.Map(f.Model.Edges.Entities, func(item *ent.Entity, index int) fs.Entity { return fs.NewEntity(item) }) } func (f *File) PrimaryEntity() fs.Entity { primary, _ := lo.Find(f.Model.Edges.Entities, func(item *ent.Entity) bool { return item.Type == int(types.EntityTypeVersion) && item.ID == f.Model.PrimaryEntity }) if primary != nil { return fs.NewEntity(primary) } return fs.NewEmptyEntity(f.Owner()) } func (f *File) PrimaryEntityID() int { return f.Model.PrimaryEntity } func (f *File) FolderSummary() *fs.FolderSummary { return f.FileFolderSummary } func (f *File) Capabilities() *boolset.BooleanSet { return f.CapabilitiesBs } func newFile(parent *File, model *ent.File) *File { f := filePool.Get().(*File) f.Model = model if parent != nil { f.Parent = parent parent.mu.Lock() parent.Children[model.Name] = f if parent.Path[pathIndexUser] != nil { f.Path[pathIndexUser] = parent.Path[pathIndexUser].Join(model.Name) } if parent.Path[pathIndexRoot] != nil { f.Path[pathIndexRoot] = parent.Path[pathIndexRoot].Join(model.Name) } f.CapabilitiesBs = parent.CapabilitiesBs f.mu = parent.mu parent.mu.Unlock() } else { f.mu = &sync.Mutex{} } return f } func newParentFile(parent *ent.File, child *File) *File { newParent := newFile(nil, parent) newParent.Children[child.Name()] = child child.Parent = newParent newParent.mu = child.mu return newParent } func (f *File) Recycle() { for _, child := range f.Children { child.Recycle() } f.Model = nil f.Children = make(map[string]*File) f.Path[0] = nil f.Path[1] = nil f.Parent = nil f.OwnerModel = nil f.IsUserRoot = false f.mu = nil filePool.Put(f) }