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.

user_space_upload.go 6.0 kB

3 months ago
3 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. package uploader
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "math"
  7. "time"
  8. "github.com/samber/lo"
  9. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  10. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  11. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch/exec"
  12. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
  13. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/ops2"
  14. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
  15. corrpc "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/rpc/coordinator"
  16. stgtypes "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  17. jcstypes "gitlink.org.cn/cloudream/jcs-pub/common/types"
  18. )
  19. func (u *Uploader) UserSpaceUpload(userSpaceID jcstypes.UserSpaceID, rootPath jcstypes.JPath, targetBktID jcstypes.BucketID, newPkgName string, uploadAffinity jcstypes.UserSpaceID) (*jcstypes.Package, error) {
  20. srcSpace := u.spaceMeta.Get(userSpaceID)
  21. if srcSpace == nil {
  22. return nil, fmt.Errorf("user space %d not found", userSpaceID)
  23. }
  24. pkg, err := db.DoTx01(u.db, func(tx db.SQLContext) (jcstypes.Package, error) {
  25. _, err := u.db.Bucket().GetByID(tx, targetBktID)
  26. if err != nil {
  27. return jcstypes.Package{}, err
  28. }
  29. return u.db.Package().Create(tx, targetBktID, newPkgName, time.Now())
  30. })
  31. if err != nil {
  32. return nil, fmt.Errorf("creating package: %w", err)
  33. }
  34. delPkg := func() {
  35. u.db.Package().Delete(u.db.DefCtx(), pkg.PackageID)
  36. }
  37. spaceIDs, err := u.db.UserSpace().GetAllIDs(u.db.DefCtx())
  38. if err != nil {
  39. delPkg()
  40. return nil, fmt.Errorf("getting user space ids: %w", err)
  41. }
  42. spaceDetails := u.spaceMeta.GetMany(spaceIDs)
  43. spaceDetails = lo.Filter(spaceDetails, func(e *jcstypes.UserSpaceDetail, i int) bool {
  44. return e != nil && e.UserSpace.ShardStore != nil
  45. })
  46. var uploadSpaces []UploadSpaceInfo
  47. // TODO 应该要分离Standalone模式和RecommendHub为nil的情况
  48. if !stgglb.StandaloneMode && srcSpace.RecommendHub != nil {
  49. coorCli := stgglb.CoordinatorRPCPool.Get()
  50. defer coorCli.Release()
  51. resp, cerr := coorCli.GetHubConnectivities(context.Background(), corrpc.ReqGetHubConnectivities([]jcstypes.HubID{srcSpace.RecommendHub.HubID}))
  52. if cerr != nil {
  53. delPkg()
  54. return nil, fmt.Errorf("getting hub connectivities: %w", cerr.ToError())
  55. }
  56. cons := make(map[jcstypes.HubID]jcstypes.HubConnectivity)
  57. for _, c := range resp.Connectivities {
  58. cons[c.ToHubID] = c
  59. }
  60. for _, space := range spaceDetails {
  61. latency := time.Duration(math.MaxInt64)
  62. con, ok := cons[space.RecommendHub.HubID]
  63. if ok && con.Latency != nil {
  64. latency = time.Duration(*con.Latency * float32(time.Millisecond))
  65. }
  66. uploadSpaces = append(uploadSpaces, UploadSpaceInfo{
  67. Space: *space,
  68. Delay: latency,
  69. IsSameLocation: space.UserSpace.Storage.GetLocation() == srcSpace.UserSpace.Storage.GetLocation(),
  70. })
  71. }
  72. } else {
  73. for _, space := range spaceDetails {
  74. uploadSpaces = append(uploadSpaces, UploadSpaceInfo{
  75. Space: *space,
  76. IsSameLocation: space.UserSpace.Storage.GetLocation() == srcSpace.UserSpace.Storage.GetLocation(),
  77. })
  78. }
  79. }
  80. if len(uploadSpaces) == 0 {
  81. delPkg()
  82. return nil, fmt.Errorf("user no available userspaces")
  83. }
  84. targetSapce := u.chooseUploadStorage(uploadSpaces, uploadAffinity)
  85. store, err := u.stgPool.GetBaseStore(srcSpace)
  86. if err != nil {
  87. delPkg()
  88. return nil, fmt.Errorf("getting base store: %w", err)
  89. }
  90. mutex, err := u.pubLock.BeginMutex().UserSpace().Buzy(srcSpace.UserSpace.UserSpaceID).Buzy(targetSapce.Space.UserSpace.UserSpaceID).End().Lock()
  91. if err != nil {
  92. delPkg()
  93. return nil, fmt.Errorf("acquire lock: %w", err)
  94. }
  95. defer mutex.Unlock()
  96. dirReader := store.ReadDir(rootPath)
  97. var adds []db.AddObjectEntry
  98. entries := make([]stgtypes.DirEntry, 0, 50)
  99. for {
  100. eof := false
  101. for len(entries) < 50 {
  102. entry, err := dirReader.Next()
  103. if err == io.EOF {
  104. eof = true
  105. break
  106. }
  107. if err != nil {
  108. delPkg()
  109. return nil, fmt.Errorf("reading dir: %w", err)
  110. }
  111. entries = append(entries, entry)
  112. }
  113. as, err := u.uploadFromBaseStore(srcSpace, &targetSapce.Space, entries, rootPath)
  114. if err != nil {
  115. delPkg()
  116. return nil, fmt.Errorf("uploading from base store: %w", err)
  117. }
  118. adds = append(adds, as...)
  119. entries = entries[:0]
  120. if eof {
  121. break
  122. }
  123. }
  124. _, err = db.DoTx21(u.db, u.db.Object().BatchAdd, pkg.PackageID, adds)
  125. if err != nil {
  126. delPkg()
  127. return nil, fmt.Errorf("adding objects: %w", err)
  128. }
  129. return &pkg, nil
  130. }
  131. func (u *Uploader) uploadFromBaseStore(srcSpace *jcstypes.UserSpaceDetail, targetSpace *jcstypes.UserSpaceDetail, entries []stgtypes.DirEntry, rootPath jcstypes.JPath) ([]db.AddObjectEntry, error) {
  132. ft := ioswitch2.FromTo{}
  133. for _, e := range entries {
  134. // 可以考虑增加一个配置项来控制是否上传空目录
  135. if e.IsDir {
  136. continue
  137. }
  138. ft.AddFrom(ioswitch2.NewFromBaseStore(*srcSpace, e.Path))
  139. ft.AddTo(ioswitch2.NewToShardStore(*targetSpace, ioswitch2.RawStream(), e.Path.String()))
  140. }
  141. plans := exec.NewPlanBuilder()
  142. err := parser.Parse(ft, plans)
  143. if err != nil {
  144. return nil, fmt.Errorf("parsing plan: %w", err)
  145. }
  146. exeCtx := exec.NewExecContext()
  147. exec.SetValueByType(exeCtx, u.stgPool)
  148. ret, err := plans.Execute(exeCtx).Wait(context.Background())
  149. if err != nil {
  150. return nil, fmt.Errorf("executing plan: %w", err)
  151. }
  152. adds := make([]db.AddObjectEntry, 0, len(ret.Stored))
  153. for _, e := range entries {
  154. if e.IsDir {
  155. continue
  156. }
  157. pat := e.Path.Clone()
  158. pat.PopFrontN(rootPath.Len() - 1)
  159. // 如果对象路径和RootPath相同(即RootPath是一个文件),则用文件名作为对象Path
  160. if pat.Len() > 1 {
  161. pat.PopFrontN(1)
  162. }
  163. info := ret.Get(e.Path.String()).(*ops2.FileInfoValue)
  164. adds = append(adds, db.AddObjectEntry{
  165. Path: pat.String(),
  166. Size: info.Size,
  167. FileHash: info.Hash,
  168. CreateTime: time.Now(),
  169. UserSpaceIDs: []jcstypes.UserSpaceID{targetSpace.UserSpace.UserSpaceID},
  170. })
  171. }
  172. return adds, nil
  173. }

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