299 lines
7.8 KiB
Go
299 lines
7.8 KiB
Go
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
|
||
}
|