package cache import ( "errors" "os" "path/filepath" "sync" "syscall" "time" "gitlink.org.cn/cloudream/common/pkgs/logger" "gitlink.org.cn/cloudream/common/pkgs/trie" 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/common/pkgs/db2" "gitlink.org.cn/cloudream/storage/common/pkgs/downloader" ) type CacheEntry interface { fuse.FsEntry // 在虚拟文件系统中的路径,即不包含缓存目录的路径 PathComps() []string // 不再使用本缓存条目 // Release() } type CacheEntryInfo struct { PathComps []string Size int64 Mode os.FileMode ModTime time.Time IsDir bool } type Cache struct { db *db2.DB downloader *downloader.Downloader cacheDataDir string cacheMetaDir string activeCache *trie.Trie[*CacheFile] lock *sync.RWMutex } func NewCache(db *db2.DB, downloader *downloader.Downloader, cacheDataDir, cacheMetaDir string) *Cache { return &Cache{ db: db, downloader: downloader, cacheDataDir: cacheDataDir, cacheMetaDir: cacheMetaDir, activeCache: trie.NewTrie[*CacheFile](), lock: &sync.RWMutex{}, } } func (c *Cache) GetCacheDataPath(comps ...string) string { comps2 := make([]string, len(comps)+1) comps2[0] = c.cacheDataDir copy(comps2[1:], comps) return filepath.Join(comps2...) } func (c *Cache) GetCacheDataPathComps(comps ...string) []string { comps2 := make([]string, len(comps)+1) comps2[0] = c.cacheDataDir copy(comps2[1:], comps) return comps2 } func (c *Cache) GetCacheMetaPath(comps ...string) string { comps2 := make([]string, len(comps)+1) comps2[0] = c.cacheMetaDir copy(comps2[1:], comps) return filepath.Join(comps2...) } func (c *Cache) GetCacheMetaPathComps(comps ...string) []string { comps2 := make([]string, len(comps)+1) comps2[0] = c.cacheMetaDir copy(comps2[1:], comps) return comps2 } // 获取指定位置的缓存条目信息。如果路径不存在,则返回nil。 func (c *Cache) Stat(pathComps []string) *CacheEntryInfo { c.lock.RLock() defer c.lock.RUnlock() node, ok := c.activeCache.WalkEnd(pathComps) if ok && node.Value != nil { info := node.Value.Info() return &info } metaPath := c.GetCacheMetaPath(pathComps...) stat, err := os.Stat(metaPath) if err != nil { // TODO 日志记录 return nil } if stat.IsDir() { info, err := loadCacheDirInfo(c, pathComps) if err != nil { return nil } return info } info, err := loadCacheFileInfo(c, pathComps) if err != nil { return nil } return info } // 创建一个缓存文件。如果文件已经存在,则会覆盖已有文件。如果加载过程中发生了错误,或者目标位置是一个目录,则会返回nil。 func (c *Cache) CreateFile(pathComps []string) *CacheFile { c.lock.Lock() defer c.lock.Unlock() ch, err := createNewCacheFile(c, pathComps) if err != nil { // TODO 日志记录 return nil } c.activeCache.CreateWords(pathComps).Value = ch return ch } // 尝试加载缓存文件,如果文件不存在,则使用obj的信息创建一个新缓存文件,而如果obj为nil,那么会返回nil。 func (c *Cache) LoadFile(pathComps []string, obj *cdssdk.Object) *CacheFile { c.lock.Lock() defer c.lock.Unlock() node, ok := c.activeCache.WalkEnd(pathComps) if ok && node.Value != nil { return node.Value } ch, err := loadCacheFile(c, pathComps) if err == nil { ch.remoteObj = obj c.activeCache.CreateWords(pathComps).Value = ch return ch } if !os.IsNotExist(err) { // TODO 日志记录 logger.Tracef("load cache file: %v", err) return nil } if obj == nil { return nil } ch, err = newCacheFileFromObject(c, pathComps, obj) if err != nil { // TODO 日志记录 logger.Tracef("make cache file from object: %v", err) return nil } c.activeCache.CreateWords(pathComps).Value = ch return ch } // 创建一个缓存目录。如果目录已经存在,则会重置目录属性。如果加载过程中发生了错误,或者目标位置是一个文件,则会返回nil func (c *Cache) CreateDir(pathComps []string) *CacheDir { c.lock.Lock() defer c.lock.Unlock() ch, err := createNewCacheDir(c, pathComps) if err != nil { // TODO 日志记录 return nil } return ch } type CreateDirOption struct { ModTime time.Time } // 加载指定缓存目录,如果目录不存在,则使用createOpt选项创建目录,而如果createOpt为nil,那么会返回nil。 func (c *Cache) LoadDir(pathComps []string, createOpt *CreateDirOption) *CacheDir { c.lock.Lock() defer c.lock.Unlock() ch, err := loadCacheDir(c, pathComps) if err == nil { return ch } if !os.IsNotExist(err) { // TODO 日志记录 return nil } if createOpt == nil { return nil } // 创建目录 ch, err = makeCacheDirFromOption(c, pathComps, *createOpt) if err != nil { // TODO 日志记录 return nil } return ch } // 加载指定路径下的所有缓存条目信息 func (c *Cache) StatMany(pathComps []string) []CacheEntryInfo { c.lock.RLock() defer c.lock.RUnlock() var infos []CacheEntryInfo exists := make(map[string]bool) node, ok := c.activeCache.WalkEnd(pathComps) if ok { for name, child := range node.WordNexts { if child.Value != nil { infos = append(infos, child.Value.Info()) exists[name] = true } } } osEns, err := os.ReadDir(c.GetCacheMetaPath(pathComps...)) if err != nil { return nil } for _, e := range osEns { if exists[e.Name()] { continue } if e.IsDir() { info, err := loadCacheDirInfo(c, append(lo2.ArrayClone(pathComps), e.Name())) if err != nil { continue } infos = append(infos, *info) } else { info, err := loadCacheFileInfo(c, append(lo2.ArrayClone(pathComps), e.Name())) if err != nil { continue } infos = append(infos, *info) } } return infos } // 删除指定路径的缓存文件或目录。删除目录时如果目录不为空,则会报错。 func (c *Cache) Remove(pathComps []string) error { c.lock.Lock() defer c.lock.Unlock() node, ok := c.activeCache.WalkEnd(pathComps) if ok { if len(node.WordNexts) > 0 { return fuse.ErrNotEmpty } if node.Value != nil { node.Value.Delete() } node.RemoveSelf(true) return nil } metaPath := c.GetCacheMetaPath(pathComps...) err := os.Remove(metaPath) if err == nil || os.IsNotExist(err) { return nil } if errors.Is(err, syscall.ENOTEMPTY) { return fuse.ErrNotEmpty } return err } // 移动指定路径的缓存文件或目录到新的路径。如果目标路径已经存在,则会报错。 // // 如果移动成功,则返回移动后的缓存文件或目录。如果文件或目录不存在,则返回nil。 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 }