package vfs import ( "context" "fmt" "os" "time" "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 FuseBucket struct { vfs *Vfs bktName string modTime time.Time mode os.FileMode } func newBucketFromCache(c cache.CacheEntryInfo, vfs *Vfs) fuse.FsDir { return &FuseBucket{ vfs: vfs, bktName: c.PathComps[len(c.PathComps)-1], modTime: c.ModTime, mode: c.Mode, } } func (r *FuseBucket) PathComps() []string { return []string{r.bktName} } func (r *FuseBucket) Name() string { return r.bktName } func (r *FuseBucket) Size() int64 { return 0 } func (r *FuseBucket) Mode() os.FileMode { return os.ModeDir | r.mode } func (r *FuseBucket) ModTime() time.Time { return r.modTime } func (r *FuseBucket) IsDir() bool { return true } func (r *FuseBucket) SetModTime(time time.Time) error { dir := r.loadCacheDir() if dir == nil { return fuse.ErrNotExists } return dir.SetModTime(time) } // 如果不存在,应该返回ErrNotExists func (r *FuseBucket) Child(ctx context.Context, name string) (fuse.FsEntry, error) { childPathComps := []string{r.bktName, name} ca := r.vfs.cache.Stat(childPathComps) if ca == nil { // TODO UserID pkg, err := r.vfs.db.Package().GetByFullName(r.vfs.db.DefCtx(), r.bktName, name) if err == nil { dir := r.vfs.cache.LoadDir(childPathComps, &cache.CreateDirOption{ ModTime: pkg.CreateTime, }) if dir == nil { return nil, fuse.ErrNotExists } return newPackageFromCache(dir.Info(), r.vfs), nil } if err == gorm.ErrRecordNotFound { return nil, fuse.ErrNotExists } return nil, err } if ca.IsDir { return newPackageFromCache(*ca, r.vfs), nil } return newFileFromCache(*ca, r.vfs), nil } func (r *FuseBucket) Children(ctx context.Context) ([]fuse.FsEntry, error) { return r.listChildren() } func (r *FuseBucket) ReadChildren() (fuse.DirReader, error) { ens, err := r.listChildren() if err != nil { return nil, err } return newFuseDirReader(ens), nil } func (r *FuseBucket) listChildren() ([]fuse.FsEntry, error) { var ens []fuse.FsEntry infos := r.vfs.cache.StatMany([]string{r.bktName}) pkgs, err := r.vfs.db.Package().GetBucketPackagesByName(r.vfs.db.DefCtx(), r.bktName) if err != nil { return nil, err } pkgMap := make(map[string]*clitypes.Package) for _, pkg := range pkgs { p := pkg pkgMap[pkg.Name] = &p } for _, c := range infos { delete(pkgMap, c.PathComps[len(c.PathComps)-1]) if c.IsDir { ens = append(ens, newPackageFromCache(c, r.vfs)) } else { ens = append(ens, newFileFromCache(c, r.vfs)) } } // 顺便创建一下文件夹 for _, pkg := range pkgMap { dir := r.vfs.cache.LoadDir([]string{r.bktName, pkg.Name}, &cache.CreateDirOption{ ModTime: pkg.CreateTime, }) if dir == nil { continue } ens = append(ens, newPackageFromCache(dir.Info(), r.vfs)) } return ens, nil } func (r *FuseBucket) NewDir(ctx context.Context, name string) (fuse.FsDir, error) { cache := r.vfs.cache.CreateDir([]string{r.bktName, name}) if cache == nil { return nil, fuse.ErrPermission } // TODO 用户ID,失败了可以打个日志 // TODO 生成系统事件 // 不关注创建是否成功,仅尝试一下 r.vfs.db.DoTx(func(tx db.SQLContext) error { db := r.vfs.db bkt, err := db.Bucket().GetByName(tx, r.bktName) if err != nil { return fmt.Errorf("get bucket: %v", err) } _, err = db.Package().Create(tx, bkt.BucketID, name) if err != nil { return fmt.Errorf("create package: %v", err) } return err }) return newPackageFromCache(cache.Info(), r.vfs), nil } func (r *FuseBucket) NewFile(ctx context.Context, name string, flags uint32) (fuse.FileHandle, uint32, error) { return newFile(r.vfs, ctx, name, r, flags) } func (r *FuseBucket) RemoveChild(ctx context.Context, name string) error { // TODO 生成系统事件 return r.vfs.db.DoTx(func(tx db.SQLContext) error { d := r.vfs.db pkg, err := d.Package().GetByFullName(tx, r.bktName, name) if err == nil { err = d.Object().HasObjectWithPrefix(tx, pkg.PackageID, "") if err == nil { return fuse.ErrNotEmpty } if err != gorm.ErrRecordNotFound { return err } err = d.Package().DeleteComplete(tx, pkg.PackageID) if err != nil { return err } } else if err != gorm.ErrRecordNotFound { return err } return r.vfs.cache.Remove([]string{r.bktName, name}) }) } func (r *FuseBucket) MoveChild(ctx context.Context, oldName string, newName string, newParent fuse.FsDir) error { newParentNode := newParent.(FuseNode) newParentPath := newParentNode.PathComps() // 仅允许在不同桶之间移动 if len(newParentPath) != 1 { return fuse.ErrNotSupported } d := r.vfs.db return d.DoTx(func(tx db.SQLContext) error { _, err := d.Package().GetByFullName(tx, 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().GetByFullName(tx, 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 { var createOpt *cache.CreateDirOption bkt, err := r.vfs.db.Bucket().GetByName(r.vfs.db.DefCtx(), r.bktName) if err == nil { createOpt = &cache.CreateDirOption{ ModTime: bkt.CreateTime, } } return r.vfs.cache.LoadDir([]string{r.bktName}, createOpt) } var _ fuse.FsDir = (*FuseBucket)(nil) var _ FuseNode = (*FuseBucket)(nil)