mirror of https://github.com/cloudreve/Cloudreve
370 lines
7.9 KiB
Go
370 lines
7.9 KiB
Go
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)
|
|
}
|