JCS-pub/client/internal/mount/vfs/fuse.go

299 lines
7.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vfs
import (
"context"
"strings"
"time"
"gitlink.org.cn/cloudream/common/utils/lo2"
"gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
"gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/fuse"
"gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/vfs/cache"
clitypes "gitlink.org.cn/cloudream/jcs-pub/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:]...)
objPrefix := objPath
if objPath != "" {
objPrefix += clitypes.ObjectPathSeparator
}
objs, coms, err := d.Object().GetByPrefixGrouped(tx, pkg.PackageID, objPrefix)
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().DeleteCompleteByPath(tx, pkg.PackageID, joinedPath)
if err != nil && err != gorm.ErrRecordNotFound {
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 err2 == fuse.ErrNotExists {
if err == fuse.ErrNotExists {
return fuse.ErrNotExists
}
return nil
}
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 err
}
err = d.Object().HasObjectWithPrefix(tx, oldPkg.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
}