package vfs import ( "context" "strings" "time" "gitlink.org.cn/cloudream/common/utils/lo2" "gitlink.org.cn/cloudream/storage2/client/internal/db" "gitlink.org.cn/cloudream/storage2/client/internal/mount/fuse" "gitlink.org.cn/cloudream/storage2/client/internal/mount/vfs/cache" clitypes "gitlink.org.cn/cloudream/storage2/client/types" "gorm.io/gorm" ) type FuseNode interface { PathComps() []string } func child(vfs *Vfs, ctx context.Context, parent FuseNode, name string) (fuse.FsEntry, error) { parentPathComps := parent.PathComps() childPathComps := lo2.AppendNew(parentPathComps, name) ca := vfs.cache.Stat(childPathComps) if ca == nil { var ret fuse.FsEntry d := vfs.db err := d.DoTx(func(tx db.SQLContext) error { pkg, err := d.Package().GetByFullName(tx, childPathComps[0], childPathComps[1]) if err != nil { if err != gorm.ErrRecordNotFound { return err } return nil } objPath := clitypes.JoinObjectPath(childPathComps[2:]...) obj, err := d.Object().GetByPath(tx, pkg.PackageID, objPath) if err == nil { ret = newFileFromObject(vfs, childPathComps, obj) return nil } if err != gorm.ErrRecordNotFound { return err } err = d.Object().HasObjectWithPrefix(tx, pkg.PackageID, objPath+clitypes.ObjectPathSeparator) if err == nil { dir := vfs.cache.LoadDir(childPathComps, &cache.CreateDirOption{ ModTime: time.Now(), }) if dir == nil { return nil } ret = newDirFromCache(dir.Info(), vfs) return nil } if err == gorm.ErrRecordNotFound { return nil } return err }) if err != nil { return nil, err } if ret == nil { return nil, fuse.ErrNotExists } return ret, nil } if ca.IsDir { return newDirFromCache(*ca, vfs), nil } return newFileFromCache(*ca, vfs), nil } func listChildren(vfs *Vfs, ctx context.Context, parent FuseNode) ([]fuse.FsEntry, error) { var ens []fuse.FsEntry myPathComps := parent.PathComps() infos := vfs.cache.StatMany(myPathComps) dbEntries := make(map[string]fuse.FsEntry) d := vfs.db d.DoTx(func(tx db.SQLContext) error { pkg, err := d.Package().GetByFullName(tx, myPathComps[0], myPathComps[1]) if err != nil { return err } objPath := clitypes.JoinObjectPath(myPathComps[2:]...) objs, coms, err := d.Object().GetByPrefixGrouped(tx, pkg.PackageID, objPath+clitypes.ObjectPathSeparator) if err != nil { return err } for _, dir := range coms { dir = strings.TrimSuffix(dir, clitypes.ObjectPathSeparator) pathComps := lo2.AppendNew(myPathComps, clitypes.BaseName(dir)) cd := vfs.cache.LoadDir(pathComps, &cache.CreateDirOption{ ModTime: time.Now(), }) if cd == nil { continue } dbEntries[dir] = newDirFromCache(cd.Info(), vfs) } for _, obj := range objs { pathComps := lo2.AppendNew(myPathComps, clitypes.BaseName(obj.Path)) file := newFileFromObject(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, vfs)) } else { ens = append(ens, newFileFromCache(c, vfs)) } } for _, e := range dbEntries { ens = append(ens, e) } return ens, nil } func newDir(vfs *Vfs, ctx context.Context, name string, parent FuseNode) (fuse.FsDir, error) { cache := vfs.cache.CreateDir(lo2.AppendNew(parent.PathComps(), name)) if cache == nil { return nil, fuse.ErrPermission } return newDirFromCache(cache.Info(), vfs), nil } func newFile(vfs *Vfs, ctx context.Context, name string, parent FuseNode, flags uint32) (fuse.FileHandle, uint32, error) { cache := vfs.cache.CreateFile(lo2.AppendNew(parent.PathComps(), name)) if cache == nil { return nil, 0, fuse.ErrPermission } defer cache.Release() // Open之后会给cache的引用计数额外+1,即使cache先于FileHandle被关闭, // 也有有FileHandle的计数保持cache的有效性 fileNode := newFileFromCache(cache.Info(), vfs) hd := cache.Open(flags) return newFileHandle(fileNode, hd), flags, nil } func removeChild(vfs *Vfs, ctx context.Context, name string, parent FuseNode) error { pathComps := lo2.AppendNew(parent.PathComps(), name) joinedPath := clitypes.JoinObjectPath(pathComps[2:]...) d := vfs.db // TODO 生成系统事件 return vfs.db.DoTx(func(tx db.SQLContext) error { pkg, err := d.Package().GetByFullName(tx, pathComps[0], pathComps[1]) if err == nil { err := d.Object().HasObjectWithPrefix(tx, pkg.PackageID, joinedPath+clitypes.ObjectPathSeparator) if err == nil { return fuse.ErrNotEmpty } if err != gorm.ErrRecordNotFound { return err } // 存储系统不会保存目录结构,所以这里是尝试删除同名文件 err = d.Object().DeleteByPath(tx, pkg.PackageID, joinedPath) if err != nil { return err } } else if err != gorm.ErrRecordNotFound { return err } return vfs.cache.Remove(pathComps) }) } func moveChild(vfs *Vfs, ctx context.Context, oldName string, oldParent FuseNode, newName string, newParent FuseNode) error { newParentPath := newParent.PathComps() newChildPath := lo2.AppendNew(newParentPath, newName) newChildPathJoined := clitypes.JoinObjectPath(newChildPath[2:]...) // 不允许移动任何内容到Package层级以上 if len(newParentPath) < 2 { return fuse.ErrNotSupported } oldChildPath := lo2.AppendNew(oldParent.PathComps(), oldName) oldChildPathJoined := clitypes.JoinObjectPath(oldChildPath[2:]...) // 先更新远程,再更新本地,因为远程使用事务更新,可以回滚,而本地不行 return vfs.db.DoTx(func(tx db.SQLContext) error { err := moveRemote(vfs, tx, oldChildPath, newParentPath, oldChildPathJoined, newChildPathJoined) if err == fuse.ErrExists { return err } if err != nil && err != fuse.ErrNotExists { return err } err2 := vfs.cache.Move(oldChildPath, newChildPath) if err == fuse.ErrNotExists && err2 == fuse.ErrNotExists { return fuse.ErrNotExists } return err2 }) } func moveRemote(vfs *Vfs, tx db.SQLContext, oldChildPath []string, newParentPath []string, oldChildPathJoined string, newChildPathJoined string) error { d := vfs.db newPkg, err := d.Package().GetByFullName(tx, newParentPath[0], newParentPath[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 } err = d.Object().HasObjectWithPrefix(tx, newPkg.PackageID, newChildPathJoined+clitypes.ObjectPathSeparator) if err == nil { return fuse.ErrExists } if err != gorm.ErrRecordNotFound { return err } // 按理来说还需要检查远程文件所在的文件夹是否存在,但对象存储是不存文件夹的,所以不检查,导致的后果就是移动时会创建不存在的文件夹 oldPkg, err := d.Package().GetByFullName(tx, oldChildPath[0], oldChildPath[1]) if err != nil { if err == gorm.ErrRecordNotFound { return fuse.ErrNotExists } return err } // 都不存在,就开始移动文件 oldObj, err := d.Object().GetByPath(tx, oldPkg.PackageID, oldChildPathJoined) if err == nil { oldObj.PackageID = newPkg.PackageID oldObj.Path = newChildPathJoined return d.Object().BatchUpdate(tx, []clitypes.Object{oldObj}) } if err == gorm.ErrRecordNotFound { return fuse.ErrNotExists } err = d.Object().HasObjectWithPrefix(tx, oldObj.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator) if err == nil { return d.Object().MoveByPrefix(tx, oldPkg.PackageID, oldChildPathJoined+clitypes.ObjectPathSeparator, newPkg.PackageID, newChildPathJoined+clitypes.ObjectPathSeparator, ) } if err == gorm.ErrRecordNotFound { return fuse.ErrNotExists } return err }