Browse Source

修复移动功能的bug

gitlink
Sydonian 8 months ago
parent
commit
7abc15b46d
11 changed files with 372 additions and 112 deletions
  1. +3
    -0
      client2/internal/mount/fuse/fuse.go
  2. +6
    -5
      client2/internal/mount/fuse/types.go
  3. +48
    -4
      client2/internal/mount/vfs/cache/cache.go
  4. +70
    -23
      client2/internal/mount/vfs/cache/file.go
  5. +34
    -7
      client2/internal/mount/vfs/fuse_bucket.go
  6. +65
    -8
      client2/internal/mount/vfs/fuse_dir.go
  7. +66
    -7
      client2/internal/mount/vfs/fuse_package.go
  8. +39
    -54
      client2/internal/mount/vfs/fuse_root.go
  9. +12
    -4
      common/pkgs/db2/object.go
  10. +5
    -0
      common/pkgs/db2/package.go
  11. +24
    -0
      common/pkgs/db2/utils.go

+ 3
- 0
client2/internal/mount/fuse/fuse.go View File

@@ -47,6 +47,9 @@ func translateError(err error) syscall.Errno {
case ErrIOError:
return syscall.EIO

case ErrNotSupported:
return syscall.ENOTSUP

default:
return syscall.EIO
}


+ 6
- 5
client2/internal/mount/fuse/types.go View File

@@ -8,11 +8,12 @@ import (
)

var (
ErrNotExists = os.ErrNotExist
ErrExists = os.ErrExist
ErrNotEmpty = errors.New("directory not empty")
ErrPermission = os.ErrPermission
ErrIOError = errors.New("I/O error")
ErrNotExists = os.ErrNotExist
ErrExists = os.ErrExist
ErrNotEmpty = errors.New("directory not empty")
ErrPermission = os.ErrPermission
ErrIOError = errors.New("I/O error")
ErrNotSupported = errors.New("not supported")
)

type Fs interface {


+ 48
- 4
client2/internal/mount/vfs/cache/cache.go View File

@@ -2,7 +2,6 @@ package cache

import (
"errors"
"fmt"
"os"
"path/filepath"
"sync"
@@ -159,7 +158,7 @@ func (c *Cache) LoadFile(pathComps []string, obj *cdssdk.Object) *CacheFile {
return nil
}

ch, err = makeCacheFileFromObject(c, pathComps, obj)
ch, err = newCacheFileFromObject(c, pathComps, obj)
if err != nil {
// TODO 日志记录
logger.Tracef("make cache file from object: %v", err)
@@ -299,6 +298,51 @@ func (c *Cache) Remove(pathComps []string) error {
// 移动指定路径的缓存文件或目录到新的路径。如果目标路径已经存在,则会报错。
//
// 如果移动成功,则返回移动后的缓存文件或目录。如果文件或目录不存在,则返回nil。
func (c *Cache) Move(pathComps []string, newPathComps []string) (CacheEntry, error) {
return nil, fmt.Errorf("not implemented")
func (c *Cache) Move(pathComps []string, newPathComps []string) error {
c.lock.Lock()
defer c.lock.Unlock()

_, ok := c.activeCache.WalkEnd(newPathComps)
if ok {
return fuse.ErrExists
}

newMetaPath := c.GetCacheMetaPath(newPathComps...)
newDataPath := c.GetCacheDataPath(newPathComps...)

_, err := os.Stat(newMetaPath)
if err == nil {
return fuse.ErrExists
} else if !os.IsNotExist(err) {
return err
}

metaPath := c.GetCacheMetaPath(pathComps...)
dataPath := c.GetCacheDataPath(pathComps...)

// 每个缓存文件持有meta文件和data文件的句柄,所以这里移动文件,不影响句柄的使用。
// 只能忽略这里的错误
os.Rename(metaPath, newMetaPath)
os.Rename(dataPath, newDataPath)

// 更新缓存
oldNode, ok := c.activeCache.WalkEnd(pathComps)
if ok {
newNode := c.activeCache.CreateWords(newPathComps)
newNode.Value = oldNode.Value
newNode.WordNexts = oldNode.WordNexts
oldNode.RemoveSelf(false)

if newNode.Value != nil {
newNode.Value.Move(newPathComps)
}
newNode.Iterate(func(path []string, node *trie.Node[*CacheFile], isWordNode bool) trie.VisitCtrl {
if node.Value != nil {
node.Value.Move(lo2.AppendNew(newPathComps, path...))
}
return trie.VisitContinue
})
}

return nil
}

+ 70
- 23
client2/internal/mount/vfs/cache/file.go View File

@@ -2,12 +2,14 @@ package cache

import (
"fmt"
"io"
"os"
"sync"
"time"

"gitlink.org.cn/cloudream/common/pkgs/logger"
cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
"gitlink.org.cn/cloudream/common/utils/io2"
"gitlink.org.cn/cloudream/common/utils/lo2"
"gitlink.org.cn/cloudream/common/utils/math2"
"gitlink.org.cn/cloudream/common/utils/serder"
@@ -88,7 +90,8 @@ type CacheFile struct {
savingMetaChan chan any
isDeleted bool

localFile *os.File
metaFile *os.File
dataFile *os.File
writeLock *sync.RWMutex
}

@@ -107,14 +110,21 @@ func createNewCacheFile(cache *Cache, pathComps []string) (*CacheFile, error) {
return nil, err
}

err = os.WriteFile(metaPath, infoData, 0644)
metaFile, err := os.OpenFile(metaPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("save cache file: %w", err)
return nil, fmt.Errorf("create cache meta file: %w", err)
}

localFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
err = io2.WriteAll(metaFile, infoData)
if err != nil {
return nil, fmt.Errorf("create cache file: %w", err)
metaFile.Close()
return nil, fmt.Errorf("save cache meta file: %w", err)
}

dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
metaFile.Close()
return nil, fmt.Errorf("create cache data file: %w", err)
}

ch := &CacheFile{
@@ -124,7 +134,8 @@ func createNewCacheFile(cache *Cache, pathComps []string) (*CacheFile, error) {
info: info,
rwLock: &sync.RWMutex{},
savingMetaChan: make(chan any, 1),
localFile: localFile,
metaFile: metaFile,
dataFile: dataFile,
writeLock: &sync.RWMutex{},
}

@@ -137,20 +148,22 @@ func loadCacheFile(cache *Cache, pathComps []string) (*CacheFile, error) {
metaPath := cache.GetCacheMetaPath(pathComps...)
dataPath := cache.GetCacheDataPath(pathComps...)

metaData, err := os.ReadFile(metaPath)
metaFile, err := os.OpenFile(metaPath, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, err
return nil, fmt.Errorf("open cache meta file: %w", err)
}

info := &FileInfo{}
err = serder.JSONToObject(metaData, info)
err = serder.JSONToObjectStream(metaFile, info)
if err != nil {
metaFile.Close()
return nil, err
}

localFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR, 0644)
dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, fmt.Errorf("create cache file: %w", err)
metaFile.Close()
return nil, fmt.Errorf("open cache data file: %w", err)
}

ch := &CacheFile{
@@ -160,7 +173,8 @@ func loadCacheFile(cache *Cache, pathComps []string) (*CacheFile, error) {
info: *info,
rwLock: &sync.RWMutex{},
savingMetaChan: make(chan any, 1),
localFile: localFile,
metaFile: metaFile,
dataFile: dataFile,
writeLock: &sync.RWMutex{},
}

@@ -169,7 +183,7 @@ func loadCacheFile(cache *Cache, pathComps []string) (*CacheFile, error) {
return ch, nil
}

func makeCacheFileFromObject(cache *Cache, pathComps []string, obj *cdssdk.Object) (*CacheFile, error) {
func newCacheFileFromObject(cache *Cache, pathComps []string, obj *cdssdk.Object) (*CacheFile, error) {
metaPath := cache.GetCacheMetaPath(pathComps...)
dataPath := cache.GetCacheDataPath(pathComps...)

@@ -185,13 +199,20 @@ func makeCacheFileFromObject(cache *Cache, pathComps []string, obj *cdssdk.Objec
return nil, err
}

err = os.WriteFile(metaPath, infoData, 0644)
metaFile, err := os.OpenFile(metaPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return nil, fmt.Errorf("save cache file: %w", err)
return nil, fmt.Errorf("create cache meta file: %w", err)
}

localFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
err = io2.WriteAll(metaFile, infoData)
if err != nil {
metaFile.Close()
return nil, fmt.Errorf("save cache meta file: %w", err)
}

dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
metaFile.Close()
return nil, fmt.Errorf("create cache file: %w", err)
}

@@ -203,7 +224,8 @@ func makeCacheFileFromObject(cache *Cache, pathComps []string, obj *cdssdk.Objec
remoteObj: obj,
rwLock: &sync.RWMutex{},
savingMetaChan: make(chan any, 1),
localFile: localFile,
metaFile: metaFile,
dataFile: dataFile,
writeLock: &sync.RWMutex{},
}

@@ -285,6 +307,19 @@ func (f *CacheFile) Delete() {
f.letSave()
}

func (f *CacheFile) Move(newPathComps []string) {
f.writeLock.Lock()
defer f.writeLock.Unlock()

f.rwLock.Lock()
defer f.rwLock.Unlock()

f.pathComps = newPathComps
f.name = newPathComps[len(newPathComps)-1]

f.letSave()
}

func (f *CacheFile) SetRemoteObject(obj *cdssdk.Object) {
f.remoteObj = obj
}
@@ -342,7 +377,7 @@ func (f *CacheFile) Truncate(size int64) error {
f.writeLock.Lock()
defer f.writeLock.Unlock()

err := f.localFile.Truncate(size)
err := f.dataFile.Truncate(size)
if err != nil {
return err
}
@@ -408,7 +443,19 @@ func (f *CacheFile) savingMeta() {
break
}

err = os.WriteFile(f.cache.GetCacheMetaPath(f.pathComps...), jsonData, 0644)
err = f.metaFile.Truncate(0)
if err != nil {
// TODO 日志
break
}

_, err = f.metaFile.Seek(0, io.SeekStart)
if err != nil {
// TODO 日志
break
}

err = io2.WriteAll(f.metaFile, jsonData)
if err != nil {
// TODO 日志
break
@@ -460,7 +507,7 @@ func (h *CacheFileReadWriter) ReadAt(buf []byte, off int64) (int, error) {
rngIdx := FirstContainsIndex(h.file.info.Segments, curOff)
if rngIdx >= 0 && h.file.info.Segments[rngIdx].End() > curOff {
readLen := math2.Min(int64(len(curBuf)), h.file.info.Segments[rngIdx].End()-curOff)
realReadLen, err := h.file.localFile.ReadAt(curBuf[:readLen], curOff)
realReadLen, err := h.file.dataFile.ReadAt(curBuf[:readLen], curOff)
totalReadLen += realReadLen
h.file.rwLock.RUnlock()

@@ -514,7 +561,7 @@ func (h *CacheFileReadWriter) ReadAt(buf []byte, off int64) (int, error) {

// 写入到本地缓存文件
writeStart := loadRng.Position - curOff
_, err = h.file.localFile.WriteAt(curBuf[writeStart:writeStart+loadRng.Length], curOff)
_, err = h.file.dataFile.WriteAt(curBuf[writeStart:writeStart+loadRng.Length], curOff)
if err != nil {
h.file.writeLock.Unlock()

@@ -549,7 +596,7 @@ func (h *CacheFileReadWriter) WriteAt(buf []byte, off int64) (int, error) {
defer h.file.writeLock.RUnlock()

// 写入到本地缓存文件
writeLen, err := h.file.localFile.WriteAt(buf, off)
writeLen, err := h.file.dataFile.WriteAt(buf, off)
if err != nil {
return writeLen, fmt.Errorf("save to local file: %w", err)
}
@@ -569,7 +616,7 @@ func (h *CacheFileReadWriter) WriteAt(buf []byte, off int64) (int, error) {
}

func (f *CacheFileReadWriter) Sync() error {
return f.file.localFile.Sync()
return f.file.dataFile.Sync()
}

func (f *CacheFileReadWriter) Close() error {


+ 34
- 7
client2/internal/mount/vfs/fuse_bucket.go View File

@@ -221,16 +221,43 @@ func (r *FuseBucket) RemoveChild(ctx context.Context, name string) error {
}

func (r *FuseBucket) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error {

// TODO 有问题

newParentNode := newParent.(FuseNode)
_, err := r.vfs.cache.Move([]string{r.bktName, oldName}, append(newParentNode.PathComps(), newName))
if err != nil {
return err
newParentPath := newParentNode.PathComps()

// 仅允许在不同桶之间移动
if len(newParentPath) != 1 {
return fuse.ErrNotSupported
}

return nil
d := r.vfs.db
return d.DoTx(func(tx db2.SQLContext) error {
_, err := d.Package().GetUserPackageByName(tx, 1, newParentPath[0], newName)
if err == nil {
// 目标节点已经存在,不能重命名,直接退出
return fuse.ErrExists
} else if err != gorm.ErrRecordNotFound {
return err
}

newBkt, err := d.Bucket().GetByName(tx, newParentPath[0])
if err == nil {
oldPkg, err := d.Package().GetUserPackageByName(tx, 1, r.bktName, oldName)
if err == nil {
err = d.Package().Move(tx, oldPkg.PackageID, newBkt.BucketID, newName)
if err != nil {
return err
}
} else if err != gorm.ErrRecordNotFound {
return err
}

} else if err != gorm.ErrRecordNotFound {
return err
}

// 有可能是移动文件,所以如果是源文件夹未找到,也尝试进行移动
return r.vfs.cache.Move([]string{r.bktName, oldName}, []string{newParentPath[0], newName})
})
}

func (r *FuseBucket) loadCacheDir() *cache.CacheDir {


+ 65
- 8
client2/internal/mount/vfs/fuse_dir.go View File

@@ -259,17 +259,74 @@ func (r *FuseDir) RemoveChild(ctx context.Context, name string) error {
}

func (r *FuseDir) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error {

// TODO 有问题
oldPathComps := append(lo2.ArrayClone(r.pathComps), oldName)

newParentNode := newParent.(FuseNode)
_, err := r.vfs.cache.Move(oldPathComps, append(newParentNode.PathComps(), newName))
if err != nil {
return err
newParentPath := newParentNode.PathComps()
newChildPath := lo2.AppendNew(newParentPath, newName)
newChildPathJoined := cdssdk.JoinObjectPath(newChildPath[2:]...)

// 不允许移动任何内容到Package层级以上
if len(newParentPath) < 2 {
return fuse.ErrNotSupported
}

return nil
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 {


+ 66
- 7
client2/internal/mount/vfs/fuse_package.go View File

@@ -8,6 +8,7 @@ import (
"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"
@@ -253,16 +254,74 @@ func (r *FusePackage) RemoveChild(ctx context.Context, name string) error {
}

func (r *FusePackage) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error {

// TODO 有问题

newParentNode := newParent.(FuseNode)
_, err := r.vfs.cache.Move([]string{r.bktName, r.pkgName, oldName}, append(newParentNode.PathComps(), newName))
if err != nil {
return err
newParentPath := newParentNode.PathComps()
newChildPath := lo2.AppendNew(newParentPath, newName)
newChildPathJoined := cdssdk.JoinObjectPath(newChildPath[2:]...)

// 不允许移动任何内容到Package层级以上
if len(newParentPath) < 2 {
return fuse.ErrNotSupported
}

return nil
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, r.bktName, r.pkgName)
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 *FusePackage) loadCacheDir() *cache.CacheDir {


+ 39
- 54
client2/internal/mount/vfs/fuse_root.go View File

@@ -12,46 +12,46 @@ import (
"gorm.io/gorm"
)

type Root struct {
type FuseRoot struct {
vfs *Vfs
}

func newRoot(vfs *Vfs) *Root {
return &Root{
func newRoot(vfs *Vfs) *FuseRoot {
return &FuseRoot{
vfs: vfs,
}
}

func (r *Root) PathComps() []string {
func (r *FuseRoot) PathComps() []string {
return []string{}
}

func (r *Root) Name() string {
func (r *FuseRoot) Name() string {
return ""
}

func (r *Root) Size() int64 {
func (r *FuseRoot) Size() int64 {
return 0
}

func (r *Root) Mode() os.FileMode {
func (r *FuseRoot) Mode() os.FileMode {
return os.ModeDir | 0755
}

func (r *Root) ModTime() time.Time {
func (r *FuseRoot) ModTime() time.Time {
return time.Now()
}

func (r *Root) IsDir() bool {
func (r *FuseRoot) IsDir() bool {
return true
}

func (r *Root) SetModTime(time time.Time) error {
func (r *FuseRoot) SetModTime(time time.Time) error {
return nil
}

// 如果不存在,应该返回ErrNotExists
func (r *Root) Child(ctx context.Context, name string) (fuse.FsEntry, error) {
func (r *FuseRoot) Child(ctx context.Context, name string) (fuse.FsEntry, error) {
ca := r.vfs.cache.Stat([]string{name})

if ca == nil {
@@ -81,11 +81,11 @@ func (r *Root) Child(ctx context.Context, name string) (fuse.FsEntry, error) {
return newFileFromCache(*ca, r.vfs), nil
}

func (r *Root) Children(ctx context.Context) ([]fuse.FsEntry, error) {
func (r *FuseRoot) Children(ctx context.Context) ([]fuse.FsEntry, error) {
return r.listChildren()
}

func (r *Root) ReadChildren() (fuse.DirReader, error) {
func (r *FuseRoot) ReadChildren() (fuse.DirReader, error) {
ens, err := r.listChildren()
if err != nil {
return nil, err
@@ -94,7 +94,7 @@ func (r *Root) ReadChildren() (fuse.DirReader, error) {
return newFuseDirReader(ens), nil
}

func (r *Root) listChildren() ([]fuse.FsEntry, error) {
func (r *FuseRoot) listChildren() ([]fuse.FsEntry, error) {
var ens []fuse.FsEntry

infos := r.vfs.cache.StatMany([]string{})
@@ -136,7 +136,7 @@ func (r *Root) listChildren() ([]fuse.FsEntry, error) {
return ens, nil
}

func (r *Root) NewDir(ctx context.Context, name string) (fuse.FsDir, error) {
func (r *FuseRoot) NewDir(ctx context.Context, name string) (fuse.FsDir, error) {
cache := r.vfs.cache.CreateDir([]string{name})
if cache == nil {
return nil, fuse.ErrPermission
@@ -150,7 +150,7 @@ func (r *Root) NewDir(ctx context.Context, name string) (fuse.FsDir, error) {
return newBucketFromCache(cache.Info(), r.vfs), nil
}

func (r *Root) NewFile(ctx context.Context, name string, flags uint32) (fuse.FileHandle, uint32, error) {
func (r *FuseRoot) NewFile(ctx context.Context, name string, flags uint32) (fuse.FileHandle, uint32, error) {
cache := r.vfs.cache.CreateFile([]string{name})
if cache == nil {
return nil, 0, fuse.ErrPermission
@@ -163,7 +163,7 @@ func (r *Root) NewFile(ctx context.Context, name string, flags uint32) (fuse.Fil
return newFileHandle(fileNode, hd), flags, nil
}

func (r *Root) RemoveChild(ctx context.Context, name string) error {
func (r *FuseRoot) RemoveChild(ctx context.Context, name string) error {
// TODO 生成系统事件
db := r.vfs.db
return r.vfs.db.DoTx(func(tx db2.SQLContext) error {
@@ -196,51 +196,36 @@ func (r *Root) RemoveChild(ctx context.Context, name string) error {
})
}

func (r *Root) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error {

// TODO 有问题

func (r *FuseRoot) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error {
newParentNode := newParent.(FuseNode)
_, err := r.vfs.cache.Move([]string{oldName}, append(newParentNode.PathComps(), newName))
if err != nil {
return err
newParentPath := newParentNode.PathComps()

// 只允许同层级内改名
if len(newParentPath) != 0 {
return fuse.ErrNotSupported
}

d := r.vfs.db
// 如果目标节点是根节点,那么就是重命名桶
if _, ok := newParent.(*Root); ok {
d.DoTx(func(tx db2.SQLContext) error {
_, err := d.Bucket().GetByName(tx, newName)
if err == nil {
// 目标节点已经存在,不能重命名,直接退出
return err
}

oldBkt, err := d.Bucket().GetByName(tx, oldName)
if err != nil {
// 源节点不存在,直接退出
return err
}
return d.DoTx(func(tx db2.SQLContext) error {
_, err := d.Bucket().GetByName(tx, newName)
if err == nil {
// 目标节点已经存在,不能重命名,直接退出
return fuse.ErrExists
}

// 不关注重命名是否成功,仅尝试一下
return d.Bucket().Rename(tx, oldBkt.BucketID, newName)
})
} else {
// TODO 做法存疑
// 其他情况则删除旧桶
d.DoTx(func(tx db2.SQLContext) error {
oldBkt, err := d.Bucket().GetByName(tx, oldName)
oldBkt, err := d.Bucket().GetByName(tx, oldName)
if err == nil {
err = d.Bucket().Rename(tx, oldBkt.BucketID, newName)
if err != nil {
// 源节点不存在,直接退出
return err
}
} else if err != gorm.ErrRecordNotFound {
return err
}

return d.Bucket().DeleteComplete(tx, oldBkt.BucketID)
})
}

return nil
return r.vfs.cache.Move([]string{oldName}, []string{newName})
})
}

var _ fuse.FsDir = (*Root)(nil)
var _ FuseNode = (*Root)(nil)
var _ fuse.FsDir = (*FuseRoot)(nil)
var _ FuseNode = (*FuseRoot)(nil)

+ 12
- 4
common/pkgs/db2/object.go View File

@@ -45,7 +45,7 @@ func (db *ObjectDB) GetByFullPath(ctx SQLContext, bktName string, pkgName string

func (db *ObjectDB) GetWithPathPrefix(ctx SQLContext, packageID cdssdk.PackageID, pathPrefix string) ([]cdssdk.Object, error) {
var ret []cdssdk.Object
err := ctx.Table("Object").Where("PackageID = ? AND Path LIKE ?", packageID, pathPrefix+"%").Order("ObjectID ASC").Find(&ret).Error
err := ctx.Table("Object").Where("PackageID = ? AND Path LIKE ?", packageID, escapeLike("", "%", pathPrefix)).Order("ObjectID ASC").Find(&ret).Error
return ret, err
}

@@ -58,7 +58,7 @@ func (db *ObjectDB) GetCommonPrefixes(ctx SQLContext, packageID cdssdk.PackageID

err := ctx.Table("Object").Select(prefixStatm+" as Prefix").
Where("PackageID = ?", packageID).
Where("Path like ?", pathPrefix+"%").
Where("Path like ?", escapeLike("", "%", pathPrefix)).
Where(prefixStatm + " <> Path").
Group("Prefix").Find(&ret).Error
if err != nil {
@@ -81,7 +81,7 @@ func (db *ObjectDB) GetDirectChildren(ctx SQLContext, packageID cdssdk.PackageID

err := ctx.Table("Object").
Where("PackageID = ?", packageID).
Where("Path like ?", pathPrefix+"%").
Where("Path like ?", escapeLike("", "%", pathPrefix)).
Where(prefixStatm + " = Path").
Find(&ret).Error
return ret, err
@@ -89,7 +89,7 @@ func (db *ObjectDB) GetDirectChildren(ctx SQLContext, packageID cdssdk.PackageID

func (db *ObjectDB) HasObjectWithPrefix(ctx SQLContext, packageID cdssdk.PackageID, pathPrefix string) (bool, error) {
var obj cdssdk.Object
err := ctx.Table("Object").Where("PackageID = ? AND Path LIKE ?", packageID, pathPrefix+"%").First(&obj).Error
err := ctx.Table("Object").Where("PackageID = ? AND Path LIKE ?", packageID, escapeLike("", "%", pathPrefix)).First(&obj).Error
if err == nil {
return true, nil
}
@@ -455,3 +455,11 @@ func (db *ObjectDB) DeleteInPackage(ctx SQLContext, packageID cdssdk.PackageID)
func (db *ObjectDB) DeleteByPath(ctx SQLContext, packageID cdssdk.PackageID, path string) error {
return ctx.Table("Object").Where("PackageID = ? AND Path = ?", packageID, path).Delete(&cdssdk.Object{}).Error
}

func (db *ObjectDB) MoveByPrefix(ctx SQLContext, oldPkgID cdssdk.PackageID, oldPrefix string, newPkgID cdssdk.PackageID, newPrefix string) error {
return ctx.Table("Object").Where("PackageID = ? AND Path LIKE ?", oldPkgID, escapeLike("", "%", oldPrefix)).
Updates(map[string]any{
"PackageID": newPkgID,
"Path": gorm.Expr("concat(?, substring(Path, ?))", newPrefix, len(oldPrefix)+1),
}).Error
}

+ 5
- 0
common/pkgs/db2/package.go View File

@@ -202,3 +202,8 @@ func (*PackageDB) HasPackageIn(ctx SQLContext, bucketID cdssdk.BucketID) (bool,
}
return true, nil
}

func (*PackageDB) Move(ctx SQLContext, packageID cdssdk.PackageID, newBktID cdssdk.BucketID, newName string) error {
err := ctx.Table("Package").Where("PackageID = ?", packageID).Update("BucketID", newBktID).Update("Name", newName).Error
return err
}

+ 24
- 0
common/pkgs/db2/utils.go View File

@@ -1,6 +1,8 @@
package db2

import (
"strings"

"gorm.io/gorm"
)

@@ -55,3 +57,25 @@ func min(a, b int) int {
}
return b
}

func escapeLike(left, right, word string) string {
var n int
for i := range word {
if c := word[i]; c == '%' || c == '_' || c == '\\' {
n++
}
}
// No characters to escape.
if n == 0 {
return left + word + right
}
var b strings.Builder
b.Grow(len(word) + n)
for _, c := range word {
if c == '%' || c == '_' || c == '\\' {
b.WriteByte('\\')
}
b.WriteRune(c)
}
return left + b.String() + right
}

Loading…
Cancel
Save