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.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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.ShardStoreStats {
  79. var stats struct {
  80. Cnt int64 `gorm:"column:Cnt; type:bigint;"`
  81. Size int64 `gorm:"column:Size; type:bigint;"`
  82. }
  83. s.ClientFileHashDB.Table("Shard").Select("Count(Hash) as Cnt, Sum(Size) as Size").Find(&stats)
  84. return stgtypes.ShardStoreStats{
  85. Status: stgtypes.StatusOK,
  86. FileCount: stats.Cnt,
  87. TotalSize: s.Config.ShardStore.MaxSize,
  88. UsedSize: stats.Size,
  89. }
  90. }
  91. func (s *LoadedStore) CreateRefs(userID jcstypes.UserID, refs []jcstypes.FileHash) ([]jcstypes.FileHash, error) {
  92. var invalidHashes []jcstypes.FileHash
  93. err := s.ClientFileHashDB.Transaction(func(tx *gorm.DB) error {
  94. var avaiHashes []jcstypes.FileHash
  95. err := tx.Table("Shard").Select("Hash").Where("Hash IN ?", refs).Find(&avaiHashes).Error
  96. if err != nil {
  97. return fmt.Errorf("check avaiable hashes: %w", err)
  98. }
  99. var avaiRefs []UserRef
  100. avaiHashesMp := make(map[jcstypes.FileHash]bool)
  101. for _, hash := range avaiHashes {
  102. avaiHashesMp[hash] = true
  103. avaiRefs = append(avaiRefs, UserRef{
  104. UserID: userID,
  105. Hash: hash,
  106. })
  107. }
  108. for _, hash := range refs {
  109. if _, ok := avaiHashesMp[hash]; !ok {
  110. invalidHashes = append(invalidHashes, hash)
  111. }
  112. }
  113. return tx.Clauses(clause.Insert{Modifier: "or ignore"}).Create(&avaiRefs).Error
  114. })
  115. return invalidHashes, err
  116. }
  117. func (s *LoadedStore) GetAllHashes() ([]jcstypes.FileHash, error) {
  118. var hashes []jcstypes.FileHash
  119. return hashes, s.ClientFileHashDB.Table("Shard").Select("Hash").Find(&hashes).Error
  120. }
  121. type Shard struct {
  122. Hash jcstypes.FileHash `gorm:"column:Hash; type:char(68); primaryKey; not null; index" json:"hash"`
  123. Path jcstypes.JPath `gorm:"column:Path; type:varchar(1024); not null; serializer:string" json:"path"`
  124. Size int64 `gorm:"column:Size; type:bigint; not null" json:"size"`
  125. }
  126. func (t Shard) TableName() string {
  127. return "Shard"
  128. }
  129. type UserRef struct {
  130. UserID jcstypes.UserID `gorm:"column:UserID; type:bigint; primaryKey; not null" json:"userID"`
  131. Hash jcstypes.FileHash `gorm:"column:Hash; type:char(68); primaryKey; not null; index" json:"hash"`
  132. }
  133. func (t UserRef) TableName() string {
  134. return "UserRef"
  135. }

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