package vfs import ( "context" "fmt" "os" "strings" "time" cdssdk "gitlink.org.cn/cloudream/common/sdks/storage" "gitlink.org.cn/cloudream/common/utils/lo2" "gitlink.org.cn/cloudream/storage/client2/internal/mount/fuse" "gitlink.org.cn/cloudream/storage/client2/internal/mount/vfs/cache" "gitlink.org.cn/cloudream/storage/common/pkgs/db2" "gorm.io/gorm" ) type FuseDir struct { vfs *Vfs pathComps []string modTime time.Time mode os.FileMode } func newDirFromCache(ch cache.CacheEntryInfo, vfs *Vfs) *FuseDir { return &FuseDir{ vfs: vfs, pathComps: ch.PathComps, modTime: ch.ModTime, mode: ch.Mode, } } func (r *FuseDir) PathComps() []string { return r.pathComps } func (r *FuseDir) Name() string { return r.pathComps[len(r.pathComps)-1] } func (r *FuseDir) Size() int64 { return 0 } func (r *FuseDir) Mode() os.FileMode { return os.ModeDir | r.mode } func (r *FuseDir) ModTime() time.Time { return r.modTime } func (r *FuseDir) IsDir() bool { return true } func (r *FuseDir) SetModTime(time time.Time) error { dir := r.loadCacheDir() if dir == nil { return fuse.ErrNotExists } return dir.SetModTime(time) } // 如果不存在,应该返回ErrNotExists func (r *FuseDir) Child(ctx context.Context, name string) (fuse.FsEntry, error) { childPathComps := lo2.AppendNew(r.pathComps, name) ca := r.vfs.cache.Stat(childPathComps) if ca == nil { var ret fuse.FsEntry db := r.vfs.db err := db.DoTx(func(tx db2.SQLContext) error { pkg, err := db.Package().GetUserPackageByName(tx, 1, r.pathComps[0], r.pathComps[1]) if err != nil { return err } objPath := cdssdk.JoinObjectPath(childPathComps[2:]...) obj, err := db.Object().GetByPath(tx, pkg.PackageID, objPath) if err == nil { ret = newFileFromObject(r.vfs, childPathComps, obj) return nil } if err != gorm.ErrRecordNotFound { return err } has, err := db.Object().HasObjectWithPrefix(tx, pkg.PackageID, objPath+cdssdk.ObjectPathSeparator) if err != nil { return err } if has { dir := r.vfs.cache.LoadDir(childPathComps, &cache.CreateDirOption{ ModTime: time.Now(), }) if dir == nil { return nil } ret = newDirFromCache(dir.Info(), r.vfs) } return nil }) if err != nil { return nil, err } if ret == nil { return nil, fuse.ErrNotExists } return ret, nil } if ca.IsDir { return newDirFromCache(*ca, r.vfs), nil } return newFileFromCache(*ca, r.vfs), nil } func (r *FuseDir) Children(ctx context.Context) ([]fuse.FsEntry, error) { return r.listChildren() } func (r *FuseDir) ReadChildren() (fuse.DirReader, error) { ens, err := r.listChildren() if err != nil { return nil, err } return newFuseDirReader(ens), nil } func (r *FuseDir) listChildren() ([]fuse.FsEntry, error) { var ens []fuse.FsEntry infos := r.vfs.cache.StatMany(r.pathComps) dbEntries := make(map[string]fuse.FsEntry) db := r.vfs.db db.DoTx(func(tx db2.SQLContext) error { // TODO UserID pkg, err := db.Package().GetUserPackageByName(tx, 1, r.pathComps[0], r.pathComps[1]) if err != nil { return err } objPath := cdssdk.JoinObjectPath(r.pathComps[2:]...) coms, err := db.Object().GetCommonPrefixes(tx, pkg.PackageID, objPath+cdssdk.ObjectPathSeparator) if err != nil { return fmt.Errorf("getting common prefixes: %w", err) } objs, err := db.Object().GetDirectChildren(tx, pkg.PackageID, objPath+cdssdk.ObjectPathSeparator) if err != nil { return fmt.Errorf("getting direct children: %w", err) } for _, dir := range coms { dir = strings.TrimSuffix(dir, cdssdk.ObjectPathSeparator) pathComps := lo2.AppendNew(r.pathComps, cdssdk.BaseName(dir)) cd := r.vfs.cache.LoadDir(pathComps, &cache.CreateDirOption{ ModTime: time.Now(), }) if cd == nil { continue } dbEntries[dir] = newDirFromCache(cd.Info(), r.vfs) } for _, obj := range objs { pathComps := lo2.AppendNew(r.pathComps, cdssdk.BaseName(obj.Path)) file := newFileFromObject(r.vfs, pathComps, obj) dbEntries[file.Name()] = file } return nil }) for _, c := range infos { delete(dbEntries, c.PathComps[len(c.PathComps)-1]) if c.IsDir { ens = append(ens, newDirFromCache(c, r.vfs)) } else { ens = append(ens, newFileFromCache(c, r.vfs)) } } for _, e := range dbEntries { ens = append(ens, e) } return ens, nil } func (r *FuseDir) NewDir(ctx context.Context, name string) (fuse.FsDir, error) { cache := r.vfs.cache.CreateDir(lo2.AppendNew(r.pathComps, name)) if cache == nil { return nil, fuse.ErrPermission } return newDirFromCache(cache.Info(), r.vfs), nil } func (r *FuseDir) NewFile(ctx context.Context, name string, flags uint32) (fuse.FileHandle, uint32, error) { cache := r.vfs.cache.CreateFile(lo2.AppendNew(r.pathComps, name)) if cache == nil { return nil, 0, fuse.ErrPermission } // Open之后会给cache的引用计数额外+1,即使cache先于FileHandle被关闭, // 也有有FileHandle的计数保持cache的有效性 fileNode := newFileFromCache(cache.Info(), r.vfs) hd := cache.Open(flags) return newFileHandle(fileNode, hd), flags, nil } func (r *FuseDir) RemoveChild(ctx context.Context, name string) error { pathComps := lo2.AppendNew(r.pathComps, name) joinedPath := cdssdk.JoinObjectPath(pathComps[2:]...) d := r.vfs.db // TODO 生成系统事件 return r.vfs.db.DoTx(func(tx db2.SQLContext) error { pkg, err := d.Package().GetUserPackageByName(tx, 1, pathComps[0], pathComps[1]) if err == nil { has, err := d.Object().HasObjectWithPrefix(tx, pkg.PackageID, joinedPath+cdssdk.ObjectPathSeparator) if err != nil { return err } if has { return fuse.ErrNotEmpty } } err = r.vfs.cache.Remove(pathComps) if err != nil { return err } if pkg.PackageID > 0 { // 存储系统不会保存目录结构,所以这里是尝试删除同名文件 d.Object().DeleteByPath(tx, pkg.PackageID, joinedPath) } return nil }) } func (r *FuseDir) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error { newParentNode := newParent.(FuseNode) newParentPath := newParentNode.PathComps() newChildPath := lo2.AppendNew(newParentPath, newName) newChildPathJoined := cdssdk.JoinObjectPath(newChildPath[2:]...) // 不允许移动任何内容到Package层级以上 if len(newParentPath) < 2 { return fuse.ErrNotSupported } oldChildPath := lo2.AppendNew(r.PathComps(), oldName) oldChildPathJoined := cdssdk.JoinObjectPath(oldChildPath[2:]...) // 先更新远程,再更新本地,因为远程使用事务更新,可以回滚,而本地不行 d := r.vfs.db return r.vfs.db.DoTx(func(tx db2.SQLContext) error { newPkg, err := d.Package().GetUserPackageByName(tx, 1, newParentPath[0], newParentPath[1]) if err != nil { if err == gorm.ErrRecordNotFound { return fuse.ErrNotExists } return err } oldPkg, err := d.Package().GetUserPackageByName(tx, 1, oldChildPath[0], oldChildPath[1]) if err != nil { if err == gorm.ErrRecordNotFound { return fuse.ErrNotExists } return err } // 检查目的文件或文件夹是否已经存在 _, err = d.Object().GetByPath(tx, newPkg.PackageID, newChildPathJoined) if err == nil { return fuse.ErrExists } has, err := d.Object().HasObjectWithPrefix(tx, newPkg.PackageID, newChildPathJoined+cdssdk.ObjectPathSeparator) if err != nil { return err } if has { return fuse.ErrExists } // 都不存在,就开始移动文件 oldObj, err := d.Object().GetByPath(tx, oldPkg.PackageID, oldChildPathJoined) if err == nil { oldObj.PackageID = newPkg.PackageID oldObj.Path = newChildPathJoined err = d.Object().BatchUpdate(tx, []cdssdk.Object{oldObj}) if err != nil { return err } } err = d.Object().MoveByPrefix(tx, oldPkg.PackageID, oldChildPathJoined+cdssdk.ObjectPathSeparator, newPkg.PackageID, newChildPathJoined+cdssdk.ObjectPathSeparator, ) if err != nil { return err } return r.vfs.cache.Move(oldChildPath, newChildPath) }) } func (r *FuseDir) loadCacheDir() *cache.CacheDir { var createOpt *cache.CreateDirOption err := r.vfs.db.DoTx(func(tx db2.SQLContext) error { pkg, err := r.vfs.db.Package().GetUserPackageByName(tx, 1, r.pathComps[0], r.pathComps[1]) if err != nil { return err } has, err := r.vfs.db.Object().HasObjectWithPrefix(tx, pkg.PackageID, cdssdk.JoinObjectPath(r.pathComps[2:]...)) if err != nil { return err } if has { createOpt = &cache.CreateDirOption{ ModTime: time.Now(), } } return nil }) if err != nil { return nil } return r.vfs.cache.LoadDir(r.pathComps, createOpt) } var _ fuse.FsDir = (*FuseDir)(nil) var _ FuseNode = (*FuseDir)(nil)