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.

shard_store.go 6.3 kB

3 months ago
3 months ago
3 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. package local
  2. import (
  3. "errors"
  4. "fmt"
  5. "io/fs"
  6. "os"
  7. "path/filepath"
  8. "sync"
  9. "time"
  10. "github.com/inhies/go-bytesize"
  11. "gitlink.org.cn/cloudream/common/pkgs/logger"
  12. stgtypes "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  13. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  14. )
  15. type ShardStore struct {
  16. detail *jcstypes.UserSpaceDetail
  17. stgRoot string
  18. storeAbsRoot string
  19. lock sync.Mutex
  20. done chan any
  21. totalShardsCnt int64
  22. totalShardsSize int64
  23. }
  24. func NewShardStore(root string, detail *jcstypes.UserSpaceDetail) (*ShardStore, error) {
  25. storeAbsRoot, err := filepath.Abs(filepath.Join(root, detail.UserSpace.WorkingDir.OSString(), stgtypes.ShardStoreWorkingDir))
  26. if err != nil {
  27. return nil, fmt.Errorf("get abs root: %w", err)
  28. }
  29. return &ShardStore{
  30. detail: detail,
  31. stgRoot: root,
  32. storeAbsRoot: storeAbsRoot,
  33. done: make(chan any, 1),
  34. }, nil
  35. }
  36. func (s *ShardStore) Start(ch *stgtypes.StorageEventChan) {
  37. s.updateStats()
  38. s.getLogger().Infof("component start, root: %v, max size: %v, shards count: %v, total size: %v",
  39. s.storeAbsRoot, s.detail.UserSpace.ShardStore.MaxSize, s.totalShardsCnt, s.totalShardsSize,
  40. )
  41. }
  42. func (s *ShardStore) Stop() {
  43. s.getLogger().Infof("component stop")
  44. }
  45. func (s *ShardStore) Store(path jcstypes.JPath, hash jcstypes.FileHash, size int64) (stgtypes.FileInfo, error) {
  46. fullTempPath := filepath.Join(s.stgRoot, path.OSString())
  47. s.lock.Lock()
  48. defer s.lock.Unlock()
  49. log := s.getLogger()
  50. log.Debugf("%v bypass uploaded, size: %v, hash: %v", fullTempPath, size, hash)
  51. blockDir := s.getFileDirFromHash(hash)
  52. err := os.MkdirAll(blockDir, 0755)
  53. if err != nil {
  54. log.Warnf("make block dir %v: %v", blockDir, err)
  55. return stgtypes.FileInfo{}, fmt.Errorf("making block dir: %w", err)
  56. }
  57. newPath := filepath.Join(blockDir, string(hash))
  58. _, err = os.Stat(newPath)
  59. if os.IsNotExist(err) {
  60. err = os.Rename(fullTempPath, newPath)
  61. if err != nil {
  62. log.Warnf("rename %v to %v: %v", fullTempPath, newPath, err)
  63. return stgtypes.FileInfo{}, fmt.Errorf("rename file: %w", err)
  64. }
  65. s.totalShardsCnt++
  66. s.totalShardsSize += size
  67. } else if err != nil {
  68. log.Warnf("get file %v stat: %v", newPath, err)
  69. return stgtypes.FileInfo{}, fmt.Errorf("get file stat: %w", err)
  70. }
  71. return stgtypes.FileInfo{
  72. Hash: hash,
  73. Size: size,
  74. Path: s.getJPathFromHash(hash),
  75. }, nil
  76. }
  77. func (s *ShardStore) Info(hash jcstypes.FileHash) (stgtypes.FileInfo, error) {
  78. s.lock.Lock()
  79. defer s.lock.Unlock()
  80. filePath := s.getFilePathFromHash(hash)
  81. info, err := os.Stat(filePath)
  82. if err != nil {
  83. return stgtypes.FileInfo{}, err
  84. }
  85. return stgtypes.FileInfo{
  86. Hash: hash,
  87. Size: info.Size(),
  88. Path: s.getJPathFromHash(hash),
  89. }, nil
  90. }
  91. func (s *ShardStore) ListAll() ([]stgtypes.FileInfo, error) {
  92. s.lock.Lock()
  93. defer s.lock.Unlock()
  94. var infos []stgtypes.FileInfo
  95. err := filepath.WalkDir(s.storeAbsRoot, func(path string, d fs.DirEntry, err error) error {
  96. if err != nil {
  97. return err
  98. }
  99. if d.IsDir() {
  100. return nil
  101. }
  102. info, err := d.Info()
  103. if err != nil {
  104. return err
  105. }
  106. fileHash, err := jcstypes.ParseHash(filepath.Base(info.Name()))
  107. if err != nil {
  108. return nil
  109. }
  110. infos = append(infos, stgtypes.FileInfo{
  111. Hash: fileHash,
  112. Size: info.Size(),
  113. Path: s.getJPathFromHash(fileHash),
  114. })
  115. return nil
  116. })
  117. if err != nil && !errors.Is(err, os.ErrNotExist) {
  118. return nil, err
  119. }
  120. return infos, nil
  121. }
  122. func (s *ShardStore) GC(avaiables []jcstypes.FileHash) error {
  123. startTime := time.Now()
  124. s.lock.Lock()
  125. defer s.lock.Unlock()
  126. avais := make(map[jcstypes.FileHash]bool)
  127. for _, hash := range avaiables {
  128. avais[hash] = true
  129. }
  130. totalCnt := 0
  131. totalSize := int64(0)
  132. err := filepath.WalkDir(s.storeAbsRoot, func(path string, d fs.DirEntry, err error) error {
  133. if err != nil {
  134. return err
  135. }
  136. if d.IsDir() {
  137. return nil
  138. }
  139. info, err := d.Info()
  140. if err != nil {
  141. return err
  142. }
  143. fileHash, err := jcstypes.ParseHash(filepath.Base(info.Name()))
  144. if err != nil {
  145. return nil
  146. }
  147. if !avais[fileHash] {
  148. err = os.Remove(path)
  149. if err != nil {
  150. s.getLogger().Warnf("remove file %v: %v", path, err)
  151. } else {
  152. totalCnt++
  153. totalSize += info.Size()
  154. s.totalShardsCnt--
  155. s.totalShardsSize -= info.Size()
  156. }
  157. }
  158. return nil
  159. })
  160. if err != nil && !errors.Is(err, os.ErrNotExist) {
  161. return err
  162. }
  163. s.getLogger().Infof(
  164. "gc %v(size: %v), time: %v, remains: %v(size: %v)",
  165. totalCnt, bytesize.ByteSize(totalSize), time.Since(startTime), s.totalShardsCnt, bytesize.ByteSize(s.totalShardsSize),
  166. )
  167. // TODO 无法保证原子性,所以删除失败只打日志
  168. return nil
  169. }
  170. func (s *ShardStore) Stats() stgtypes.ShardStoreStats {
  171. s.lock.Lock()
  172. defer s.lock.Unlock()
  173. return stgtypes.ShardStoreStats{
  174. Status: stgtypes.StatusOK,
  175. FileCount: s.totalShardsCnt,
  176. TotalSize: s.detail.UserSpace.ShardStore.MaxSize,
  177. UsedSize: s.totalShardsSize,
  178. }
  179. }
  180. func (s *ShardStore) getLogger() logger.Logger {
  181. return logger.WithField("ShardStore", "Local").WithField("Storage", s.detail.UserSpace.Storage.String())
  182. }
  183. func (s *ShardStore) getFileDirFromHash(hash jcstypes.FileHash) string {
  184. return filepath.Join(s.storeAbsRoot, hash.GetHashPrefix(2))
  185. }
  186. func (s *ShardStore) getFilePathFromHash(hash jcstypes.FileHash) string {
  187. return filepath.Join(s.storeAbsRoot, hash.GetHashPrefix(2), string(hash))
  188. }
  189. func (s *ShardStore) getJPathFromHash(hash jcstypes.FileHash) jcstypes.JPath {
  190. return s.detail.UserSpace.WorkingDir.PushNew(stgtypes.ShardStoreWorkingDir, hash.GetHashPrefix(2), string(hash))
  191. }
  192. func (s *ShardStore) updateStats() {
  193. log := s.getLogger()
  194. totalCnt := int64(0)
  195. totalSize := int64(0)
  196. err := filepath.WalkDir(s.storeAbsRoot, func(path string, d fs.DirEntry, err error) error {
  197. if err != nil {
  198. return err
  199. }
  200. if d.IsDir() {
  201. return nil
  202. }
  203. info, err := d.Info()
  204. if err != nil {
  205. return err
  206. }
  207. _, err = jcstypes.ParseHash(filepath.Base(info.Name()))
  208. if err != nil {
  209. return nil
  210. }
  211. totalCnt++
  212. totalSize += info.Size()
  213. return nil
  214. })
  215. if err != nil && !errors.Is(err, os.ErrNotExist) {
  216. log.Warnf("walk dir %v: %v", s.storeAbsRoot, err)
  217. return
  218. }
  219. s.lock.Lock()
  220. defer s.lock.Unlock()
  221. s.totalShardsCnt = totalCnt
  222. s.totalShardsSize = totalSize
  223. }

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