You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

cache.go 9.3 kB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package cache
  2. import (
  3. "errors"
  4. "os"
  5. "path/filepath"
  6. "sync"
  7. "syscall"
  8. "time"
  9. "gitlink.org.cn/cloudream/common/pkgs/logger"
  10. "gitlink.org.cn/cloudream/common/pkgs/trie"
  11. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  12. "gitlink.org.cn/cloudream/common/utils/lo2"
  13. "gitlink.org.cn/cloudream/storage/client2/internal/mount/fuse"
  14. "gitlink.org.cn/cloudream/storage/common/pkgs/db2"
  15. "gitlink.org.cn/cloudream/storage/common/pkgs/downloader"
  16. )
  17. type CacheEntry interface {
  18. fuse.FsEntry
  19. // 在虚拟文件系统中的路径,即不包含缓存目录的路径
  20. PathComps() []string
  21. }
  22. type CacheEntryInfo struct {
  23. PathComps []string
  24. Size int64
  25. Mode os.FileMode
  26. ModTime time.Time
  27. IsDir bool
  28. }
  29. type Cache struct {
  30. db *db2.DB
  31. downloader *downloader.Downloader
  32. cacheDataDir string
  33. cacheMetaDir string
  34. lock *sync.RWMutex
  35. cacheDone chan any
  36. activeCache *trie.Trie[*CacheFile]
  37. freeCache []*CacheFile
  38. }
  39. func NewCache(db *db2.DB, downloader *downloader.Downloader, cacheDataDir, cacheMetaDir string) *Cache {
  40. return &Cache{
  41. db: db,
  42. downloader: downloader,
  43. cacheDataDir: cacheDataDir,
  44. cacheMetaDir: cacheMetaDir,
  45. lock: &sync.RWMutex{},
  46. cacheDone: make(chan any),
  47. activeCache: trie.NewTrie[*CacheFile](),
  48. }
  49. }
  50. func (c *Cache) Start() {
  51. go c.clearFreeCache()
  52. }
  53. func (c *Cache) Stop() {
  54. close(c.cacheDone)
  55. }
  56. func (c *Cache) GetCacheDataPath(comps ...string) string {
  57. comps2 := make([]string, len(comps)+1)
  58. comps2[0] = c.cacheDataDir
  59. copy(comps2[1:], comps)
  60. return filepath.Join(comps2...)
  61. }
  62. func (c *Cache) GetCacheDataPathComps(comps ...string) []string {
  63. comps2 := make([]string, len(comps)+1)
  64. comps2[0] = c.cacheDataDir
  65. copy(comps2[1:], comps)
  66. return comps2
  67. }
  68. func (c *Cache) GetCacheMetaPath(comps ...string) string {
  69. comps2 := make([]string, len(comps)+1)
  70. comps2[0] = c.cacheMetaDir
  71. copy(comps2[1:], comps)
  72. return filepath.Join(comps2...)
  73. }
  74. func (c *Cache) GetCacheMetaPathComps(comps ...string) []string {
  75. comps2 := make([]string, len(comps)+1)
  76. comps2[0] = c.cacheMetaDir
  77. copy(comps2[1:], comps)
  78. return comps2
  79. }
  80. // 获取指定位置的缓存条目信息。如果路径不存在,则返回nil。
  81. func (c *Cache) Stat(pathComps []string) *CacheEntryInfo {
  82. c.lock.RLock()
  83. defer c.lock.RUnlock()
  84. node, ok := c.activeCache.WalkEnd(pathComps)
  85. if ok && node.Value != nil {
  86. info := node.Value.Info()
  87. return &info
  88. }
  89. metaPath := c.GetCacheMetaPath(pathComps...)
  90. stat, err := os.Stat(metaPath)
  91. if err != nil {
  92. // TODO 日志记录
  93. return nil
  94. }
  95. if stat.IsDir() {
  96. info, err := loadCacheDirInfo(c, pathComps)
  97. if err != nil {
  98. return nil
  99. }
  100. return info
  101. }
  102. info, err := loadCacheFileInfo(c, pathComps)
  103. if err != nil {
  104. return nil
  105. }
  106. return info
  107. }
  108. // 创建一个缓存文件。如果文件已经存在,则会覆盖已有文件。如果加载过程中发生了错误,或者目标位置是一个目录,则会返回nil。
  109. func (c *Cache) CreateFile(pathComps []string) *CacheFile {
  110. c.lock.Lock()
  111. defer c.lock.Unlock()
  112. ch, err := createNewCacheFile(c, pathComps)
  113. if err != nil {
  114. logger.Warnf("create new cache file %v: %v", pathComps, err)
  115. return nil
  116. }
  117. ch.refCount++
  118. c.activeCache.CreateWords(pathComps).Value = ch
  119. logger.Debugf("create new cache file %v", pathComps)
  120. return ch
  121. }
  122. // 尝试加载缓存文件,如果文件不存在,则使用obj的信息创建一个新缓存文件,而如果obj为nil,那么会返回nil。
  123. func (c *Cache) LoadFile(pathComps []string, obj *cdssdk.Object) *CacheFile {
  124. c.lock.Lock()
  125. defer c.lock.Unlock()
  126. node, ok := c.activeCache.WalkEnd(pathComps)
  127. if ok && node.Value != nil {
  128. return node.Value
  129. }
  130. ch, err := loadCacheFile(c, pathComps)
  131. if err == nil {
  132. ch.remoteObj = obj
  133. ch.refCount++
  134. c.activeCache.CreateWords(pathComps).Value = ch
  135. logger.Debugf("load cache %v", pathComps)
  136. return ch
  137. }
  138. if !os.IsNotExist(err) {
  139. // TODO 日志记录
  140. logger.Warnf("load cache %v: %v", pathComps, err)
  141. return nil
  142. }
  143. if obj == nil {
  144. return nil
  145. }
  146. ch, err = newCacheFileFromObject(c, pathComps, obj)
  147. if err != nil {
  148. logger.Warnf("create cache %v from object: %v", pathComps, err)
  149. return nil
  150. }
  151. ch.refCount++
  152. c.activeCache.CreateWords(pathComps).Value = ch
  153. logger.Debugf("create cache %v from object %v", pathComps, obj.ObjectID)
  154. return ch
  155. }
  156. // 创建一个缓存目录。如果目录已经存在,则会重置目录属性。如果加载过程中发生了错误,或者目标位置是一个文件,则会返回nil
  157. func (c *Cache) CreateDir(pathComps []string) *CacheDir {
  158. c.lock.Lock()
  159. defer c.lock.Unlock()
  160. ch, err := createNewCacheDir(c, pathComps)
  161. if err != nil {
  162. logger.Warnf("create cache dir: %v", err)
  163. return nil
  164. }
  165. return ch
  166. }
  167. type CreateDirOption struct {
  168. ModTime time.Time
  169. }
  170. // 加载指定缓存目录,如果目录不存在,则使用createOpt选项创建目录,而如果createOpt为nil,那么会返回nil。
  171. func (c *Cache) LoadDir(pathComps []string, createOpt *CreateDirOption) *CacheDir {
  172. c.lock.Lock()
  173. defer c.lock.Unlock()
  174. ch, err := loadCacheDir(c, pathComps)
  175. if err == nil {
  176. return ch
  177. }
  178. if !os.IsNotExist(err) {
  179. // TODO 日志记录
  180. return nil
  181. }
  182. if createOpt == nil {
  183. return nil
  184. }
  185. // 创建目录
  186. ch, err = makeCacheDirFromOption(c, pathComps, *createOpt)
  187. if err != nil {
  188. // TODO 日志记录
  189. return nil
  190. }
  191. return ch
  192. }
  193. // 加载指定路径下的所有缓存条目信息
  194. func (c *Cache) StatMany(pathComps []string) []CacheEntryInfo {
  195. c.lock.RLock()
  196. defer c.lock.RUnlock()
  197. var infos []CacheEntryInfo
  198. exists := make(map[string]bool)
  199. node, ok := c.activeCache.WalkEnd(pathComps)
  200. if ok {
  201. for name, child := range node.WordNexts {
  202. if child.Value != nil {
  203. infos = append(infos, child.Value.Info())
  204. exists[name] = true
  205. }
  206. }
  207. }
  208. osEns, err := os.ReadDir(c.GetCacheMetaPath(pathComps...))
  209. if err != nil {
  210. return nil
  211. }
  212. for _, e := range osEns {
  213. if exists[e.Name()] {
  214. continue
  215. }
  216. if e.IsDir() {
  217. info, err := loadCacheDirInfo(c, append(lo2.ArrayClone(pathComps), e.Name()))
  218. if err != nil {
  219. continue
  220. }
  221. infos = append(infos, *info)
  222. } else {
  223. info, err := loadCacheFileInfo(c, append(lo2.ArrayClone(pathComps), e.Name()))
  224. if err != nil {
  225. continue
  226. }
  227. infos = append(infos, *info)
  228. }
  229. }
  230. return infos
  231. }
  232. // 删除指定路径的缓存文件或目录。删除目录时如果目录不为空,则会报错。
  233. func (c *Cache) Remove(pathComps []string) error {
  234. c.lock.Lock()
  235. defer c.lock.Unlock()
  236. node, ok := c.activeCache.WalkEnd(pathComps)
  237. if ok {
  238. if len(node.WordNexts) > 0 {
  239. return fuse.ErrNotEmpty
  240. }
  241. if node.Value != nil {
  242. node.Value.Delete()
  243. c.freeCache = lo2.Remove(c.freeCache, node.Value)
  244. }
  245. node.RemoveSelf(true)
  246. logger.Debugf("active cache %v removed", pathComps)
  247. return nil
  248. }
  249. metaPath := c.GetCacheMetaPath(pathComps...)
  250. err := os.Remove(metaPath)
  251. if err == nil || os.IsNotExist(err) {
  252. logger.Debugf("local cache %v removed", pathComps)
  253. return nil
  254. }
  255. if errors.Is(err, syscall.ENOTEMPTY) {
  256. return fuse.ErrNotEmpty
  257. }
  258. return err
  259. }
  260. // 移动指定路径的缓存文件或目录到新的路径。如果目标路径已经存在,则会报错。
  261. //
  262. // 如果移动成功,则返回移动后的缓存文件或目录。如果文件或目录不存在,则返回nil。
  263. func (c *Cache) Move(pathComps []string, newPathComps []string) error {
  264. c.lock.Lock()
  265. defer c.lock.Unlock()
  266. _, ok := c.activeCache.WalkEnd(newPathComps)
  267. if ok {
  268. return fuse.ErrExists
  269. }
  270. newMetaPath := c.GetCacheMetaPath(newPathComps...)
  271. newDataPath := c.GetCacheDataPath(newPathComps...)
  272. _, err := os.Stat(newMetaPath)
  273. if err == nil {
  274. return fuse.ErrExists
  275. } else if !os.IsNotExist(err) {
  276. return err
  277. }
  278. metaPath := c.GetCacheMetaPath(pathComps...)
  279. dataPath := c.GetCacheDataPath(pathComps...)
  280. // 每个缓存文件持有meta文件和data文件的句柄,所以这里移动文件,不影响句柄的使用。
  281. // 只能忽略这里的错误
  282. os.Rename(metaPath, newMetaPath)
  283. os.Rename(dataPath, newDataPath)
  284. // 更新缓存
  285. oldNode, ok := c.activeCache.WalkEnd(pathComps)
  286. if ok {
  287. newNode := c.activeCache.CreateWords(newPathComps)
  288. newNode.Value = oldNode.Value
  289. newNode.WordNexts = oldNode.WordNexts
  290. oldNode.RemoveSelf(false)
  291. if newNode.Value != nil {
  292. newNode.Value.Move(newPathComps)
  293. }
  294. newNode.Iterate(func(path []string, node *trie.Node[*CacheFile], isWordNode bool) trie.VisitCtrl {
  295. if node.Value != nil {
  296. node.Value.Move(lo2.AppendNew(newPathComps, path...))
  297. }
  298. return trie.VisitContinue
  299. })
  300. }
  301. logger.Debugf("cache moved: %v -> %v", pathComps, newPathComps)
  302. return nil
  303. }
  304. func (c *Cache) clearFreeCache() {
  305. ticker := time.NewTicker(time.Second * 5)
  306. defer ticker.Stop()
  307. for {
  308. select {
  309. case _, ok := <-c.cacheDone:
  310. if !ok {
  311. return
  312. }
  313. case <-ticker.C:
  314. }
  315. c.lock.Lock()
  316. for i, ch := range c.freeCache {
  317. if time.Since(ch.freeTime) > time.Second*30 {
  318. ch.Free()
  319. node, _ := c.activeCache.WalkEnd(ch.PathComps())
  320. node.RemoveSelf(true)
  321. c.freeCache[i] = nil
  322. logger.Debugf("cache %v freed", ch.PathComps())
  323. }
  324. }
  325. c.freeCache = lo2.RemoveAllDefault(c.freeCache)
  326. c.lock.Unlock()
  327. }
  328. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。