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.go 8.2 kB

6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
6 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package services
  2. import (
  3. "context"
  4. "fmt"
  5. "path"
  6. "strings"
  7. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  8. "gitlink.org.cn/cloudream/common/pkgs/logger"
  9. "gitlink.org.cn/cloudream/common/pkgs/trie"
  10. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  11. clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
  12. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  13. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader/strategy"
  14. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  15. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/distlock/reqbuilder"
  16. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
  17. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
  18. hubmq "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/mq/hub"
  19. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  20. )
  21. type UserSpaceService struct {
  22. *Service
  23. }
  24. func (svc *Service) UserSpaceSvc() *UserSpaceService {
  25. return &UserSpaceService{Service: svc}
  26. }
  27. func (svc *UserSpaceService) Get(userspaceID clitypes.UserSpaceID) (clitypes.UserSpace, error) {
  28. return svc.DB.UserSpace().GetByID(svc.DB.DefCtx(), userspaceID)
  29. }
  30. func (svc *UserSpaceService) GetByName(name string) (clitypes.UserSpace, error) {
  31. return svc.DB.UserSpace().GetByName(svc.DB.DefCtx(), name)
  32. }
  33. func (svc *UserSpaceService) LoadPackage(packageID clitypes.PackageID, userspaceID clitypes.UserSpaceID, rootPath string) error {
  34. coorCli, err := stgglb.CoordinatorMQPool.Acquire()
  35. if err != nil {
  36. return fmt.Errorf("new coordinator client: %w", err)
  37. }
  38. defer stgglb.CoordinatorMQPool.Release(coorCli)
  39. destStg := svc.UserSpaceMeta.Get(userspaceID)
  40. if destStg == nil {
  41. return fmt.Errorf("userspace not found: %d", userspaceID)
  42. }
  43. if destStg.MasterHub == nil {
  44. return fmt.Errorf("userspace %v has no master hub", userspaceID)
  45. }
  46. details, err := db.DoTx11(svc.DB, svc.DB.Object().GetPackageObjectDetails, packageID)
  47. if err != nil {
  48. return err
  49. }
  50. var pinned []clitypes.ObjectID
  51. plans := exec.NewPlanBuilder()
  52. for _, obj := range details {
  53. strg, err := svc.StrategySelector.Select(strategy.Request{
  54. Detail: obj,
  55. DestHub: destStg.MasterHub.HubID,
  56. })
  57. if err != nil {
  58. return fmt.Errorf("select download strategy: %w", err)
  59. }
  60. ft := ioswitch2.NewFromTo()
  61. switch strg := strg.(type) {
  62. case *strategy.DirectStrategy:
  63. ft.AddFrom(ioswitch2.NewFromShardstore(strg.Detail.Object.FileHash, *strg.UserSpace.MasterHub, strg.UserSpace, ioswitch2.RawStream()))
  64. case *strategy.ECReconstructStrategy:
  65. for i, b := range strg.Blocks {
  66. ft.AddFrom(ioswitch2.NewFromShardstore(b.FileHash, *strg.UserSpaces[i].MasterHub, strg.UserSpaces[i], ioswitch2.ECStream(b.Index)))
  67. ft.ECParam = &strg.Redundancy
  68. }
  69. default:
  70. return fmt.Errorf("unsupported download strategy: %T", strg)
  71. }
  72. ft.AddTo(ioswitch2.NewToPublicStore(*destStg.MasterHub, *destStg, path.Join(rootPath, obj.Object.Path)))
  73. // 顺便保存到同存储服务的分片存储中
  74. if destStg.UserSpace.ShardStore != nil {
  75. ft.AddTo(ioswitch2.NewToShardStore(*destStg.MasterHub, *destStg, ioswitch2.RawStream(), ""))
  76. pinned = append(pinned, obj.Object.ObjectID)
  77. }
  78. err = parser.Parse(ft, plans)
  79. if err != nil {
  80. return fmt.Errorf("parse plan: %w", err)
  81. }
  82. }
  83. mutex, err := reqbuilder.NewBuilder().
  84. Shard().Buzy(userspaceID).
  85. MutexLock(svc.PubLock)
  86. if err != nil {
  87. return fmt.Errorf("acquire locks failed, err: %w", err)
  88. }
  89. defer mutex.Unlock()
  90. // 记录访问统计
  91. for _, obj := range details {
  92. svc.AccessStat.AddAccessCounter(obj.Object.ObjectID, packageID, userspaceID, 1)
  93. }
  94. drv := plans.Execute(exec.NewExecContext())
  95. _, err = drv.Wait(context.Background())
  96. if err != nil {
  97. return err
  98. }
  99. return nil
  100. }
  101. func (svc *UserSpaceService) SpaceToSpace(srcSpaceID clitypes.UserSpaceID, srcPath string, dstSpaceID clitypes.UserSpaceID, dstPath string) (clitypes.SpaceToSpaceResult, error) {
  102. srcSpace := svc.UserSpaceMeta.Get(srcSpaceID)
  103. if srcSpace == nil {
  104. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("source userspace not found: %d", srcSpaceID)
  105. }
  106. if srcSpace.MasterHub == nil {
  107. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("source userspace %v has no master hub", srcSpaceID)
  108. }
  109. srcSpaceCli, err := stgglb.HubMQPool.Acquire(srcSpace.MasterHub.HubID)
  110. if err != nil {
  111. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("new source userspace client: %w", err)
  112. }
  113. defer stgglb.HubMQPool.Release(srcSpaceCli)
  114. dstSpace := svc.UserSpaceMeta.Get(dstSpaceID)
  115. if dstSpace == nil {
  116. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("destination userspace not found: %d", dstSpaceID)
  117. }
  118. if dstSpace.MasterHub == nil {
  119. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("destination userspace %v has no master hub", dstSpaceID)
  120. }
  121. dstSpaceCli, err := stgglb.HubMQPool.Acquire(dstSpace.MasterHub.HubID)
  122. if err != nil {
  123. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("new destination userspace client: %w", err)
  124. }
  125. defer stgglb.HubMQPool.Release(dstSpaceCli)
  126. srcPath = strings.Trim(srcPath, cdssdk.ObjectPathSeparator)
  127. dstPath = strings.Trim(dstPath, cdssdk.ObjectPathSeparator)
  128. if srcPath == "" {
  129. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("source path is empty")
  130. }
  131. if dstPath == "" {
  132. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("destination path is empty")
  133. }
  134. listAllResp, err := srcSpaceCli.PublicStoreListAll(&hubmq.PublicStoreListAll{
  135. UserSpace: *srcSpace,
  136. Path: srcPath,
  137. })
  138. if err != nil {
  139. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("list all from source userspace: %w", err)
  140. }
  141. srcPathComps := clitypes.SplitObjectPath(srcPath)
  142. srcDirCompLen := len(srcPathComps) - 1
  143. entryTree := trie.NewTrie[*types.PublicStoreEntry]()
  144. for _, e := range listAllResp.Entries {
  145. pa, ok := strings.CutSuffix(e.Path, clitypes.ObjectPathSeparator)
  146. comps := clitypes.SplitObjectPath(pa)
  147. e.Path = pa
  148. e2 := e
  149. entryTree.CreateWords(comps[srcDirCompLen:]).Value = &e2
  150. e2.IsDir = e2.IsDir || ok
  151. }
  152. entryTree.Iterate(func(path []string, node *trie.Node[*types.PublicStoreEntry], isWordNode bool) trie.VisitCtrl {
  153. if node.Value == nil {
  154. return trie.VisitContinue
  155. }
  156. if node.Value.IsDir && len(node.WordNexts) > 0 {
  157. node.Value = nil
  158. return trie.VisitContinue
  159. }
  160. if !node.Value.IsDir && len(node.WordNexts) == 0 {
  161. node.WordNexts = nil
  162. }
  163. return trie.VisitContinue
  164. })
  165. var filePathes []string
  166. var dirPathes []string
  167. entryTree.Iterate(func(path []string, node *trie.Node[*types.PublicStoreEntry], isWordNode bool) trie.VisitCtrl {
  168. if node.Value == nil {
  169. return trie.VisitContinue
  170. }
  171. if node.Value.IsDir {
  172. dirPathes = append(dirPathes, node.Value.Path)
  173. } else {
  174. filePathes = append(filePathes, node.Value.Path)
  175. }
  176. return trie.VisitContinue
  177. })
  178. var success []string
  179. var failed []string
  180. for _, f := range filePathes {
  181. newPath := strings.Replace(f, srcPath, dstPath, 1)
  182. ft := ioswitch2.NewFromTo()
  183. ft.AddFrom(ioswitch2.NewFromPublicStore(*srcSpace.MasterHub, *srcSpace, f))
  184. ft.AddTo(ioswitch2.NewToPublicStore(*dstSpace.MasterHub, *dstSpace, newPath))
  185. plans := exec.NewPlanBuilder()
  186. err = parser.Parse(ft, plans)
  187. if err != nil {
  188. failed = append(failed, f)
  189. logger.Warnf("s2s: parse plan of file %v: %v", f, err)
  190. continue
  191. }
  192. _, err = plans.Execute(exec.NewExecContext()).Wait(context.Background())
  193. if err != nil {
  194. failed = append(failed, f)
  195. logger.Warnf("s2s: execute plan of file %v: %v", f, err)
  196. continue
  197. }
  198. success = append(success, f)
  199. }
  200. newDirPathes := make([]string, 0, len(dirPathes))
  201. for i := range dirPathes {
  202. newDirPathes = append(newDirPathes, strings.Replace(dirPathes[i], srcPath, dstPath, 1))
  203. }
  204. mkdirResp, err := dstSpaceCli.PublicStoreMkdirs(&hubmq.PublicStoreMkdirs{
  205. UserSpace: *dstSpace,
  206. Pathes: newDirPathes,
  207. })
  208. if err != nil {
  209. failed = append(failed, dirPathes...)
  210. logger.Warnf("s2s: mkdirs to destination userspace: %v", err)
  211. } else {
  212. for i := range dirPathes {
  213. if mkdirResp.Successes[i] {
  214. success = append(success, dirPathes[i])
  215. } else {
  216. failed = append(failed, dirPathes[i])
  217. }
  218. }
  219. }
  220. return clitypes.SpaceToSpaceResult{
  221. Success: success,
  222. Failed: failed,
  223. }, nil
  224. }

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