| @@ -47,6 +47,9 @@ func translateError(err error) syscall.Errno { | |||
| case ErrIOError: | |||
| return syscall.EIO | |||
| case ErrNotSupported: | |||
| return syscall.ENOTSUP | |||
| default: | |||
| return syscall.EIO | |||
| } | |||
| @@ -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 { | |||
| @@ -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 | |||
| } | |||
| @@ -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 { | |||
| @@ -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 { | |||
| @@ -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 { | |||
| @@ -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 { | |||
| @@ -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) | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||
| @@ -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 | |||
| } | |||