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.

base_store.go 5.0 kB

10 months ago
10 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. package s3
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/sha256"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "time"
  10. "github.com/aws/aws-sdk-go-v2/aws"
  11. "github.com/aws/aws-sdk-go-v2/service/s3"
  12. s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
  13. "gitlink.org.cn/cloudream/common/pkgs/logger"
  14. "gitlink.org.cn/cloudream/common/utils/io2"
  15. clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
  16. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  17. )
  18. const (
  19. ModTimeHeader = "X-JCS-ModTime"
  20. )
  21. type BaseStore struct {
  22. Detail *clitypes.UserSpaceDetail
  23. Bucket string
  24. cli *s3.Client
  25. opt BaseStoreOption
  26. }
  27. type BaseStoreOption struct {
  28. UseAWSSha256 bool // 能否直接使用AWS提供的SHA256校验,如果不行,则使用本地计算。默认使用本地计算。
  29. }
  30. func NewBaseStore(detail *clitypes.UserSpaceDetail, cli *s3.Client, bkt string, opt BaseStoreOption) (*BaseStore, error) {
  31. return &BaseStore{
  32. Detail: detail,
  33. Bucket: bkt,
  34. cli: cli,
  35. opt: opt,
  36. }, nil
  37. }
  38. func (s *BaseStore) Write(pat clitypes.JPath, stream io.Reader, opt types.WriteOption) (types.FileInfo, error) {
  39. key := pat
  40. meta := make(map[string]string)
  41. if opt.ModTime.IsZero() {
  42. mt, _ := time.Now().MarshalText()
  43. meta[ModTimeHeader] = string(mt)
  44. } else {
  45. mt, err := opt.ModTime.MarshalText()
  46. if err != nil {
  47. return types.FileInfo{}, err
  48. }
  49. meta[ModTimeHeader] = string(mt)
  50. }
  51. counter := io2.Counter(stream)
  52. if s.opt.UseAWSSha256 {
  53. resp, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
  54. Bucket: aws.String(s.Bucket),
  55. Key: aws.String(key.String()),
  56. Body: counter,
  57. ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256,
  58. Metadata: meta,
  59. })
  60. if err != nil {
  61. return types.FileInfo{}, err
  62. }
  63. if resp.ChecksumSHA256 == nil {
  64. return types.FileInfo{}, errors.New("SHA256 checksum not found in response")
  65. }
  66. hash, err := DecodeBase64Hash(*resp.ChecksumSHA256)
  67. if err != nil {
  68. return types.FileInfo{}, fmt.Errorf("decode SHA256 checksum: %v", err)
  69. }
  70. return types.FileInfo{
  71. Path: key,
  72. Hash: clitypes.NewFullHash(hash),
  73. Size: counter.Count(),
  74. }, nil
  75. }
  76. hashStr := io2.NewReadHasher(sha256.New(), counter)
  77. _, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
  78. Bucket: aws.String(s.Bucket),
  79. Key: aws.String(key.String()),
  80. Body: counter,
  81. Metadata: meta,
  82. })
  83. if err != nil {
  84. return types.FileInfo{}, err
  85. }
  86. return types.FileInfo{
  87. Path: key,
  88. Hash: clitypes.NewFullHash(hashStr.Sum()),
  89. Size: counter.Count(),
  90. }, nil
  91. }
  92. func (s *BaseStore) Read(pat clitypes.JPath, opt types.OpenOption) (io.ReadCloser, error) {
  93. key := pat
  94. rngStr := fmt.Sprintf("bytes=%d-", opt.Offset)
  95. if opt.Length >= 0 {
  96. rngStr += fmt.Sprintf("%d", opt.Offset+opt.Length-1)
  97. }
  98. resp, err := s.cli.GetObject(context.TODO(), &s3.GetObjectInput{
  99. Bucket: aws.String(s.Bucket),
  100. Key: aws.String(key.String()),
  101. Range: aws.String(rngStr),
  102. })
  103. if err != nil {
  104. return nil, err
  105. }
  106. return resp.Body, nil
  107. }
  108. func (s *BaseStore) Mkdir(path clitypes.JPath) error {
  109. _, err := s.cli.PutObject(context.TODO(), &s3.PutObjectInput{
  110. Bucket: aws.String(s.Bucket),
  111. Key: aws.String(path.String() + "/"),
  112. Body: bytes.NewReader([]byte{}),
  113. })
  114. return err
  115. }
  116. func (s *BaseStore) ReadDir(path clitypes.JPath) types.DirReader {
  117. return &DirReader{
  118. cli: s.cli,
  119. bucket: s.Bucket,
  120. rootPath: path.Clone(),
  121. }
  122. }
  123. func (s *BaseStore) CleanTemps() {
  124. log := s.getLogger()
  125. var deletes []s3types.ObjectIdentifier
  126. deleteObjs := make(map[string]s3types.Object)
  127. var marker *string
  128. for {
  129. resp, err := s.cli.ListObjects(context.Background(), &s3.ListObjectsInput{
  130. Bucket: aws.String(s.Bucket),
  131. Prefix: aws.String(JoinKey(s.Detail.UserSpace.WorkingDir.String(), types.TempWorkingDir, "/")),
  132. Marker: marker,
  133. })
  134. if err != nil {
  135. log.Warnf("read temp dir: %v", err)
  136. return
  137. }
  138. for _, obj := range resp.Contents {
  139. deletes = append(deletes, s3types.ObjectIdentifier{
  140. Key: obj.Key,
  141. })
  142. deleteObjs[*obj.Key] = obj
  143. }
  144. if !*resp.IsTruncated {
  145. break
  146. }
  147. marker = resp.NextMarker
  148. }
  149. if len(deletes) == 0 {
  150. return
  151. }
  152. resp, err := s.cli.DeleteObjects(context.Background(), &s3.DeleteObjectsInput{
  153. Bucket: aws.String(s.Bucket),
  154. Delete: &s3types.Delete{
  155. Objects: deletes,
  156. },
  157. })
  158. if err != nil {
  159. log.Warnf("delete temp files: %v", err)
  160. return
  161. }
  162. for _, del := range resp.Deleted {
  163. obj := deleteObjs[*del.Key]
  164. log.Infof("remove unused temp file %v, size: %v, last mod time: %v", *obj.Key, *obj.Size, *obj.LastModified)
  165. }
  166. }
  167. func (s *BaseStore) Test() error {
  168. _, err := s.cli.ListObjects(context.Background(), &s3.ListObjectsInput{
  169. Bucket: aws.String(s.Bucket),
  170. Prefix: aws.String(""),
  171. MaxKeys: aws.Int32(1),
  172. })
  173. return err
  174. }
  175. func (s *BaseStore) getLogger() logger.Logger {
  176. return logger.WithField("BaseStore", "S3").WithField("Storage", s.Detail.UserSpace.Storage.String())
  177. }

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