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.

pub_shards.go 4.5 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. package pubshards
  2. import (
  3. "fmt"
  4. stgtypes "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  5. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  6. "gorm.io/gorm"
  7. "gorm.io/gorm/clause"
  8. )
  9. type LoadedStore struct {
  10. ShardStore stgtypes.ShardStore
  11. Config jcstypes.PubShards
  12. PasswordHash []byte
  13. ClientFileHashDB *gorm.DB
  14. }
  15. func (s *LoadedStore) StoreShard(userID jcstypes.UserID, path jcstypes.JPath, hash jcstypes.FileHash, size int64) (stgtypes.FileInfo, error) {
  16. info, err := s.ShardStore.Store(path, hash, size)
  17. if err != nil {
  18. return stgtypes.FileInfo{}, err
  19. }
  20. err = s.ClientFileHashDB.Transaction(func(tx *gorm.DB) error {
  21. err = tx.Clauses(clause.Insert{Modifier: "or ignore"}).Create(&Shard{
  22. Path: info.Path,
  23. Hash: hash,
  24. Size: size,
  25. }).Error
  26. if err != nil {
  27. return err
  28. }
  29. err = tx.Clauses(clause.Insert{Modifier: "or ignore"}).Create(&UserRef{
  30. UserID: userID,
  31. Hash: hash,
  32. }).Error
  33. if err != nil {
  34. return err
  35. }
  36. return nil
  37. })
  38. if err != nil {
  39. return stgtypes.FileInfo{}, err
  40. }
  41. return info, nil
  42. }
  43. func (s *LoadedStore) InfoShard(hash jcstypes.FileHash) (stgtypes.FileInfo, error) {
  44. return s.ShardStore.Info(hash)
  45. }
  46. func (s *LoadedStore) ListUserAll(userID jcstypes.UserID) ([]stgtypes.FileInfo, error) {
  47. var files []Shard
  48. err := s.ClientFileHashDB.Table("UserRef").Select("Shard.*").Joins("join Shard on UserRef.Hash = Shard.Hash").Where("UserRef.UserID = ?", userID).Find(&files).Error
  49. if err != nil {
  50. return nil, err
  51. }
  52. infos := make([]stgtypes.FileInfo, len(files))
  53. for i, file := range files {
  54. infos[i] = stgtypes.FileInfo{
  55. Path: file.Path,
  56. Size: file.Size,
  57. Hash: file.Hash,
  58. }
  59. }
  60. return infos, nil
  61. }
  62. func (s *LoadedStore) GC(userID jcstypes.UserID, fileHashes []jcstypes.FileHash) error {
  63. // 从总体上看,GC是在ListUserAll之后调用的,FileHashes的内容只会小于已有的内容,所以创建Ref时可以不检查Hash是否存在
  64. return s.ClientFileHashDB.Transaction(func(tx *gorm.DB) error {
  65. if err := tx.Delete(&UserRef{}, "UserID = ?", userID).Error; err != nil {
  66. return fmt.Errorf("delete all user refs: %w", err)
  67. }
  68. refs := make([]UserRef, len(fileHashes))
  69. for i, hash := range fileHashes {
  70. refs[i] = UserRef{
  71. UserID: userID,
  72. Hash: hash,
  73. }
  74. }
  75. return tx.Clauses(clause.Insert{Modifier: "or ignore"}).Create(&refs).Error
  76. })
  77. }
  78. func (s *LoadedStore) GetUserStats(userID jcstypes.UserID) stgtypes.Stats {
  79. cnt := int64(0)
  80. size := int64(0)
  81. s.ClientFileHashDB.Table("Shard").Select("Count(Hash), Sum(Size)").Find(&cnt)
  82. return stgtypes.Stats{
  83. Status: stgtypes.StatusOK,
  84. FileCount: cnt,
  85. TotalSize: s.Config.ShardStore.MaxSize,
  86. UsedSize: size,
  87. }
  88. }
  89. func (s *LoadedStore) CreateRefs(userID jcstypes.UserID, refs []jcstypes.FileHash) ([]jcstypes.FileHash, error) {
  90. var invalidHashes []jcstypes.FileHash
  91. err := s.ClientFileHashDB.Transaction(func(tx *gorm.DB) error {
  92. var avaiHashes []jcstypes.FileHash
  93. err := tx.Table("Shard").Select("Hash").Where("Hash IN ?", refs).Find(&avaiHashes).Error
  94. if err != nil {
  95. return fmt.Errorf("check avaiable hashes: %w", err)
  96. }
  97. var avaiRefs []UserRef
  98. avaiHashesMp := make(map[jcstypes.FileHash]bool)
  99. for _, hash := range avaiHashes {
  100. avaiHashesMp[hash] = true
  101. avaiRefs = append(avaiRefs, UserRef{
  102. UserID: userID,
  103. Hash: hash,
  104. })
  105. }
  106. for _, hash := range refs {
  107. if _, ok := avaiHashesMp[hash]; !ok {
  108. invalidHashes = append(invalidHashes, hash)
  109. }
  110. }
  111. return tx.Clauses(clause.Insert{Modifier: "or ignore"}).Create(&avaiRefs).Error
  112. })
  113. return invalidHashes, err
  114. }
  115. func (s *LoadedStore) GetAllHashes() ([]jcstypes.FileHash, error) {
  116. var hashes []jcstypes.FileHash
  117. return hashes, s.ClientFileHashDB.Table("Shard").Select("Hash").Find(&hashes).Error
  118. }
  119. type Shard struct {
  120. Hash jcstypes.FileHash `gorm:"column:Hash; type:char(68); primaryKey; not null; index" json:"hash"`
  121. Path jcstypes.JPath `gorm:"column:Path; type:varchar(1024); not null; serializer:string" json:"path"`
  122. Size int64 `gorm:"column:Size; type:bigint; not null" json:"size"`
  123. }
  124. func (t Shard) TableName() string {
  125. return "Shard"
  126. }
  127. type UserRef struct {
  128. UserID jcstypes.UserID `gorm:"column:UserID; type:bigint; primaryKey; not null" json:"userID"`
  129. Hash jcstypes.FileHash `gorm:"column:Hash; type:char(68); primaryKey; not null; index" json:"hash"`
  130. }
  131. func (t UserRef) TableName() string {
  132. return "UserRef"
  133. }

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