|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- 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
- }
-
- 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
- lock *sync.RWMutex
- cacheDone chan any
- activeCache *trie.Trie[*CacheFile]
- freeCache []*CacheFile
- }
-
- func NewCache(db *db2.DB, downloader *downloader.Downloader, cacheDataDir, cacheMetaDir string) *Cache {
- return &Cache{
- db: db,
- downloader: downloader,
- cacheDataDir: cacheDataDir,
- cacheMetaDir: cacheMetaDir,
- lock: &sync.RWMutex{},
- cacheDone: make(chan any),
- activeCache: trie.NewTrie[*CacheFile](),
- }
- }
-
- func (c *Cache) Start() {
- go c.clearFreeCache()
- }
-
- func (c *Cache) Stop() {
- close(c.cacheDone)
- }
-
- 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 {
- logger.Warnf("create new cache file %v: %v", pathComps, err)
- return nil
- }
-
- ch.refCount++
- c.activeCache.CreateWords(pathComps).Value = ch
-
- logger.Debugf("create new cache file %v", pathComps)
- 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
- ch.refCount++
- c.activeCache.CreateWords(pathComps).Value = ch
-
- logger.Debugf("load cache %v", pathComps)
- return ch
- }
-
- if !os.IsNotExist(err) {
- // TODO 日志记录
- logger.Warnf("load cache %v: %v", pathComps, err)
- return nil
- }
-
- if obj == nil {
- return nil
- }
-
- ch, err = newCacheFileFromObject(c, pathComps, obj)
- if err != nil {
- logger.Warnf("create cache %v from object: %v", pathComps, err)
- return nil
- }
-
- ch.refCount++
- c.activeCache.CreateWords(pathComps).Value = ch
-
- logger.Debugf("create cache %v from object %v", pathComps, obj.ObjectID)
- 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 {
- logger.Warnf("create cache dir: %v", err)
- 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()
- c.freeCache = lo2.Remove(c.freeCache, node.Value)
- }
-
- node.RemoveSelf(true)
-
- logger.Debugf("active cache %v removed", pathComps)
- return nil
- }
-
- metaPath := c.GetCacheMetaPath(pathComps...)
- err := os.Remove(metaPath)
- if err == nil || os.IsNotExist(err) {
- logger.Debugf("local cache %v removed", pathComps)
- 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
- })
- }
-
- logger.Debugf("cache moved: %v -> %v", pathComps, newPathComps)
- return nil
- }
-
- func (c *Cache) clearFreeCache() {
- ticker := time.NewTicker(time.Second * 5)
- defer ticker.Stop()
-
- for {
-
- select {
- case _, ok := <-c.cacheDone:
- if !ok {
- return
- }
-
- case <-ticker.C:
- }
-
- c.lock.Lock()
- for i, ch := range c.freeCache {
- if time.Since(ch.freeTime) > time.Second*30 {
- ch.Free()
- node, _ := c.activeCache.WalkEnd(ch.PathComps())
- node.RemoveSelf(true)
- c.freeCache[i] = nil
-
- logger.Debugf("cache %v freed", ch.PathComps())
- }
- }
- c.freeCache = lo2.RemoveAllDefault(c.freeCache)
- c.lock.Unlock()
- }
- }
|